| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Hardware driver for NI 660x devices |
| */ |
| |
| /* |
| * Driver: ni_660x |
| * Description: National Instruments 660x counter/timer boards |
| * Devices: [National Instruments] PCI-6601 (ni_660x), PCI-6602, PXI-6602, |
| * PCI-6608, PXI-6608, PCI-6624, 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: Mon, 16 Jan 2017 14:00:43 +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" |
| #include "ni_routes.h" |
| |
| /* See Register-Level Programmer Manual page 3.1 */ |
| enum ni_660x_register { |
| /* see enum ni_gpct_register */ |
| NI660X_STC_DIO_PARALLEL_INPUT = NITIO_NUM_REGS, |
| NI660X_STC_DIO_OUTPUT, |
| NI660X_STC_DIO_CONTROL, |
| NI660X_STC_DIO_SERIAL_INPUT, |
| 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, |
| }; |
| |
| #define NI660X_CLK_CFG_COUNTER_SWAP BIT(21) |
| |
| #define NI660X_GLOBAL_INT_COUNTER0 BIT(8) |
| #define NI660X_GLOBAL_INT_COUNTER1 BIT(9) |
| #define NI660X_GLOBAL_INT_COUNTER2 BIT(10) |
| #define NI660X_GLOBAL_INT_COUNTER3 BIT(11) |
| #define NI660X_GLOBAL_INT_CASCADE BIT(29) |
| #define NI660X_GLOBAL_INT_GLOBAL_POL BIT(30) |
| #define NI660X_GLOBAL_INT_GLOBAL BIT(31) |
| |
| #define NI660X_DMA_CFG_SEL(_c, _s) (((_s) & 0x1f) << (8 * (_c))) |
| #define NI660X_DMA_CFG_SEL_MASK(_c) NI660X_DMA_CFG_SEL((_c), 0x1f) |
| #define NI660X_DMA_CFG_SEL_NONE(_c) NI660X_DMA_CFG_SEL((_c), 0x1f) |
| #define NI660X_DMA_CFG_RESET(_c) NI660X_DMA_CFG_SEL((_c), 0x80) |
| |
| #define NI660X_IO_CFG(x) (NI660X_IO_CFG_0_1 + ((x) / 2)) |
| #define NI660X_IO_CFG_OUT_SEL(_c, _s) (((_s) & 0x3) << (((_c) % 2) ? 0 : 8)) |
| #define NI660X_IO_CFG_OUT_SEL_MASK(_c) NI660X_IO_CFG_OUT_SEL((_c), 0x3) |
| #define NI660X_IO_CFG_IN_SEL(_c, _s) (((_s) & 0x7) << (((_c) % 2) ? 4 : 12)) |
| #define NI660X_IO_CFG_IN_SEL_MASK(_c) NI660X_IO_CFG_IN_SEL((_c), 0x7) |
| |
| struct ni_660x_register_data { |
| int offset; /* Offset from base address from GPCT chip */ |
| char size; /* 2 or 4 bytes */ |
| }; |
| |
| static const struct ni_660x_register_data ni_660x_reg_data[NI660X_NUM_REGS] = { |
| [NITIO_G0_INT_ACK] = { 0x004, 2 }, /* write */ |
| [NITIO_G0_STATUS] = { 0x004, 2 }, /* read */ |
| [NITIO_G1_INT_ACK] = { 0x006, 2 }, /* write */ |
| [NITIO_G1_STATUS] = { 0x006, 2 }, /* read */ |
| [NITIO_G01_STATUS] = { 0x008, 2 }, /* read */ |
| [NITIO_G0_CMD] = { 0x00c, 2 }, /* write */ |
| [NI660X_STC_DIO_PARALLEL_INPUT] = { 0x00e, 2 }, /* read */ |
| [NITIO_G1_CMD] = { 0x00e, 2 }, /* write */ |
| [NITIO_G0_HW_SAVE] = { 0x010, 4 }, /* read */ |
| [NITIO_G1_HW_SAVE] = { 0x014, 4 }, /* read */ |
| [NI660X_STC_DIO_OUTPUT] = { 0x014, 2 }, /* write */ |
| [NI660X_STC_DIO_CONTROL] = { 0x016, 2 }, /* write */ |
| [NITIO_G0_SW_SAVE] = { 0x018, 4 }, /* read */ |
| [NITIO_G1_SW_SAVE] = { 0x01c, 4 }, /* read */ |
| [NITIO_G0_MODE] = { 0x034, 2 }, /* write */ |
| [NITIO_G01_STATUS1] = { 0x036, 2 }, /* read */ |
| [NITIO_G1_MODE] = { 0x036, 2 }, /* write */ |
| [NI660X_STC_DIO_SERIAL_INPUT] = { 0x038, 2 }, /* read */ |
| [NITIO_G0_LOADA] = { 0x038, 4 }, /* write */ |
| [NITIO_G01_STATUS2] = { 0x03a, 2 }, /* read */ |
| [NITIO_G0_LOADB] = { 0x03c, 4 }, /* write */ |
| [NITIO_G1_LOADA] = { 0x040, 4 }, /* write */ |
| [NITIO_G1_LOADB] = { 0x044, 4 }, /* write */ |
| [NITIO_G0_INPUT_SEL] = { 0x048, 2 }, /* write */ |
| [NITIO_G1_INPUT_SEL] = { 0x04a, 2 }, /* write */ |
| [NITIO_G0_AUTO_INC] = { 0x088, 2 }, /* write */ |
| [NITIO_G1_AUTO_INC] = { 0x08a, 2 }, /* write */ |
| [NITIO_G01_RESET] = { 0x090, 2 }, /* write */ |
| [NITIO_G0_INT_ENA] = { 0x092, 2 }, /* write */ |
| [NITIO_G1_INT_ENA] = { 0x096, 2 }, /* write */ |
| [NITIO_G0_CNT_MODE] = { 0x0b0, 2 }, /* write */ |
| [NITIO_G1_CNT_MODE] = { 0x0b2, 2 }, /* write */ |
| [NITIO_G0_GATE2] = { 0x0b4, 2 }, /* write */ |
| [NITIO_G1_GATE2] = { 0x0b6, 2 }, /* write */ |
| [NITIO_G0_DMA_CFG] = { 0x0b8, 2 }, /* write */ |
| [NITIO_G0_DMA_STATUS] = { 0x0b8, 2 }, /* read */ |
| [NITIO_G1_DMA_CFG] = { 0x0ba, 2 }, /* write */ |
| [NITIO_G1_DMA_STATUS] = { 0x0ba, 2 }, /* read */ |
| [NITIO_G2_INT_ACK] = { 0x104, 2 }, /* write */ |
| [NITIO_G2_STATUS] = { 0x104, 2 }, /* read */ |
| [NITIO_G3_INT_ACK] = { 0x106, 2 }, /* write */ |
| [NITIO_G3_STATUS] = { 0x106, 2 }, /* read */ |
| [NITIO_G23_STATUS] = { 0x108, 2 }, /* read */ |
| [NITIO_G2_CMD] = { 0x10c, 2 }, /* write */ |
| [NITIO_G3_CMD] = { 0x10e, 2 }, /* write */ |
| [NITIO_G2_HW_SAVE] = { 0x110, 4 }, /* read */ |
| [NITIO_G3_HW_SAVE] = { 0x114, 4 }, /* read */ |
| [NITIO_G2_SW_SAVE] = { 0x118, 4 }, /* read */ |
| [NITIO_G3_SW_SAVE] = { 0x11c, 4 }, /* read */ |
| [NITIO_G2_MODE] = { 0x134, 2 }, /* write */ |
| [NITIO_G23_STATUS1] = { 0x136, 2 }, /* read */ |
| [NITIO_G3_MODE] = { 0x136, 2 }, /* write */ |
| [NITIO_G2_LOADA] = { 0x138, 4 }, /* write */ |
| [NITIO_G23_STATUS2] = { 0x13a, 2 }, /* read */ |
| [NITIO_G2_LOADB] = { 0x13c, 4 }, /* write */ |
| [NITIO_G3_LOADA] = { 0x140, 4 }, /* write */ |
| [NITIO_G3_LOADB] = { 0x144, 4 }, /* write */ |
| [NITIO_G2_INPUT_SEL] = { 0x148, 2 }, /* write */ |
| [NITIO_G3_INPUT_SEL] = { 0x14a, 2 }, /* write */ |
| [NITIO_G2_AUTO_INC] = { 0x188, 2 }, /* write */ |
| [NITIO_G3_AUTO_INC] = { 0x18a, 2 }, /* write */ |
| [NITIO_G23_RESET] = { 0x190, 2 }, /* write */ |
| [NITIO_G2_INT_ENA] = { 0x192, 2 }, /* write */ |
| [NITIO_G3_INT_ENA] = { 0x196, 2 }, /* write */ |
| [NITIO_G2_CNT_MODE] = { 0x1b0, 2 }, /* write */ |
| [NITIO_G3_CNT_MODE] = { 0x1b2, 2 }, /* write */ |
| [NITIO_G2_GATE2] = { 0x1b4, 2 }, /* write */ |
| [NITIO_G3_GATE2] = { 0x1b6, 2 }, /* write */ |
| [NITIO_G2_DMA_CFG] = { 0x1b8, 2 }, /* write */ |
| [NITIO_G2_DMA_STATUS] = { 0x1b8, 2 }, /* read */ |
| [NITIO_G3_DMA_CFG] = { 0x1ba, 2 }, /* write */ |
| [NITIO_G3_DMA_STATUS] = { 0x1ba, 2 }, /* read */ |
| [NI660X_DIO32_INPUT] = { 0x414, 4 }, /* read */ |
| [NI660X_DIO32_OUTPUT] = { 0x510, 4 }, /* write */ |
| [NI660X_CLK_CFG] = { 0x73c, 4 }, /* write */ |
| [NI660X_GLOBAL_INT_STATUS] = { 0x754, 4 }, /* read */ |
| [NI660X_DMA_CFG] = { 0x76c, 4 }, /* write */ |
| [NI660X_GLOBAL_INT_CFG] = { 0x770, 4 }, /* write */ |
| [NI660X_IO_CFG_0_1] = { 0x77c, 2 }, /* read/write */ |
| [NI660X_IO_CFG_2_3] = { 0x77e, 2 }, /* read/write */ |
| [NI660X_IO_CFG_4_5] = { 0x780, 2 }, /* read/write */ |
| [NI660X_IO_CFG_6_7] = { 0x782, 2 }, /* read/write */ |
| [NI660X_IO_CFG_8_9] = { 0x784, 2 }, /* read/write */ |
| [NI660X_IO_CFG_10_11] = { 0x786, 2 }, /* read/write */ |
| [NI660X_IO_CFG_12_13] = { 0x788, 2 }, /* read/write */ |
| [NI660X_IO_CFG_14_15] = { 0x78a, 2 }, /* read/write */ |
| [NI660X_IO_CFG_16_17] = { 0x78c, 2 }, /* read/write */ |
| [NI660X_IO_CFG_18_19] = { 0x78e, 2 }, /* read/write */ |
| [NI660X_IO_CFG_20_21] = { 0x790, 2 }, /* read/write */ |
| [NI660X_IO_CFG_22_23] = { 0x792, 2 }, /* read/write */ |
| [NI660X_IO_CFG_24_25] = { 0x794, 2 }, /* read/write */ |
| [NI660X_IO_CFG_26_27] = { 0x796, 2 }, /* read/write */ |
| [NI660X_IO_CFG_28_29] = { 0x798, 2 }, /* read/write */ |
| [NI660X_IO_CFG_30_31] = { 0x79a, 2 }, /* read/write */ |
| [NI660X_IO_CFG_32_33] = { 0x79c, 2 }, /* read/write */ |
| [NI660X_IO_CFG_34_35] = { 0x79e, 2 }, /* read/write */ |
| [NI660X_IO_CFG_36_37] = { 0x7a0, 2 }, /* read/write */ |
| [NI660X_IO_CFG_38_39] = { 0x7a2, 2 } /* read/write */ |
| }; |
| |
| #define NI660X_CHIP_OFFSET 0x800 |
| |
| enum ni_660x_boardid { |
| BOARD_PCI6601, |
| BOARD_PCI6602, |
| BOARD_PXI6602, |
| BOARD_PCI6608, |
| BOARD_PXI6608, |
| BOARD_PCI6624, |
| BOARD_PXI6624 |
| }; |
| |
| struct ni_660x_board { |
| const char *name; |
| unsigned int 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_PCI6608] = { |
| .name = "PCI-6608", |
| .n_chips = 2, |
| }, |
| [BOARD_PXI6608] = { |
| .name = "PXI-6608", |
| .n_chips = 2, |
| }, |
| [BOARD_PCI6624] = { |
| .name = "PCI-6624", |
| .n_chips = 2, |
| }, |
| [BOARD_PXI6624] = { |
| .name = "PXI-6624", |
| .n_chips = 2, |
| }, |
| }; |
| |
| #define NI660X_NUM_PFI_CHANNELS 40 |
| |
| /* there are only up to 3 dma channels, but the register layout allows for 4 */ |
| #define NI660X_MAX_DMA_CHANNEL 4 |
| |
| #define NI660X_COUNTERS_PER_CHIP 4 |
| #define NI660X_MAX_CHIPS 2 |
| #define NI660X_MAX_COUNTERS (NI660X_MAX_CHIPS * \ |
| NI660X_COUNTERS_PER_CHIP) |
| |
| struct ni_660x_private { |
| struct mite *mite; |
| struct ni_gpct_device *counter_dev; |
| struct mite_ring *ring[NI660X_MAX_CHIPS][NI660X_COUNTERS_PER_CHIP]; |
| /* protects mite channel request/release */ |
| spinlock_t mite_channel_lock; |
| /* prevents races between interrupt and comedi_poll */ |
| spinlock_t interrupt_lock; |
| unsigned int dma_cfg[NI660X_MAX_CHIPS]; |
| unsigned int io_cfg[NI660X_NUM_PFI_CHANNELS]; |
| u64 io_dir; |
| struct ni_route_tables routing_tables; |
| }; |
| |
| static void ni_660x_write(struct comedi_device *dev, unsigned int chip, |
| unsigned int bits, unsigned int reg) |
| { |
| unsigned int addr = (chip * NI660X_CHIP_OFFSET) + |
| ni_660x_reg_data[reg].offset; |
| |
| if (ni_660x_reg_data[reg].size == 2) |
| writew(bits, dev->mmio + addr); |
| else |
| writel(bits, dev->mmio + addr); |
| } |
| |
| static unsigned int ni_660x_read(struct comedi_device *dev, |
| unsigned int chip, unsigned int reg) |
| { |
| unsigned int addr = (chip * NI660X_CHIP_OFFSET) + |
| ni_660x_reg_data[reg].offset; |
| |
| if (ni_660x_reg_data[reg].size == 2) |
| return readw(dev->mmio + addr); |
| return readl(dev->mmio + addr); |
| } |
| |
| static void ni_660x_gpct_write(struct ni_gpct *counter, unsigned int bits, |
| enum ni_gpct_register reg) |
| { |
| struct comedi_device *dev = counter->counter_dev->dev; |
| |
| ni_660x_write(dev, counter->chip_index, bits, reg); |
| } |
| |
| static unsigned int ni_660x_gpct_read(struct ni_gpct *counter, |
| enum ni_gpct_register reg) |
| { |
| struct comedi_device *dev = counter->counter_dev->dev; |
| |
| return ni_660x_read(dev, counter->chip_index, reg); |
| } |
| |
| static inline void ni_660x_set_dma_channel(struct comedi_device *dev, |
| unsigned int mite_channel, |
| struct ni_gpct *counter) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| unsigned int chip = counter->chip_index; |
| |
| devpriv->dma_cfg[chip] &= ~NI660X_DMA_CFG_SEL_MASK(mite_channel); |
| devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL(mite_channel, |
| counter->counter_index); |
| ni_660x_write(dev, chip, devpriv->dma_cfg[chip] | |
| NI660X_DMA_CFG_RESET(mite_channel), |
| NI660X_DMA_CFG); |
| } |
| |
| static inline void ni_660x_unset_dma_channel(struct comedi_device *dev, |
| unsigned int mite_channel, |
| struct ni_gpct *counter) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| unsigned int chip = counter->chip_index; |
| |
| devpriv->dma_cfg[chip] &= ~NI660X_DMA_CFG_SEL_MASK(mite_channel); |
| devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL_NONE(mite_channel); |
| ni_660x_write(dev, chip, devpriv->dma_cfg[chip], NI660X_DMA_CFG); |
| } |
| |
| 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; |
| struct mite_ring *ring; |
| struct mite_channel *mite_chan; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&devpriv->mite_channel_lock, flags); |
| ring = devpriv->ring[counter->chip_index][counter->counter_index]; |
| mite_chan = mite_request_channel(devpriv->mite, ring); |
| 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 int 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 = NI660X_CLK_CFG_COUNTER_SWAP; |
| |
| ni_660x_write(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 int i; |
| unsigned long flags; |
| |
| if (!dev->attached) |
| return IRQ_NONE; |
| /* make sure dev->attached is checked before doing anything else */ |
| smp_mb(); |
| |
| /* lock to avoid race with comedi_poll */ |
| spin_lock_irqsave(&devpriv->interrupt_lock, flags); |
| for (i = 0; i < dev->n_subdevices; ++i) { |
| s = &dev->subdevices[i]; |
| if (s->type == COMEDI_SUBD_COUNTER) |
| 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_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; |
| struct mite_ring *ring; |
| int ret; |
| |
| ring = devpriv->ring[counter->chip_index][counter->counter_index]; |
| ret = mite_buf_change(ring, s); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| static int ni_660x_allocate_private(struct comedi_device *dev) |
| { |
| struct ni_660x_private *devpriv; |
| unsigned int 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); |
| for (i = 0; i < NI660X_NUM_PFI_CHANNELS; ++i) |
| devpriv->io_cfg[i] = NI_660X_PFI_OUTPUT_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 int i; |
| unsigned int j; |
| |
| for (i = 0; i < board->n_chips; ++i) { |
| for (j = 0; j < NI660X_COUNTERS_PER_CHIP; ++j) { |
| devpriv->ring[i][j] = mite_alloc_ring(devpriv->mite); |
| if (!devpriv->ring[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 int i; |
| unsigned int j; |
| |
| for (i = 0; i < board->n_chips; ++i) { |
| for (j = 0; j < NI660X_COUNTERS_PER_CHIP; ++j) |
| mite_free_ring(devpriv->ring[i][j]); |
| } |
| } |
| |
| static int ni_660x_dio_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int shift = CR_CHAN(insn->chanspec); |
| unsigned int mask = data[0] << shift; |
| unsigned int bits = data[1] << shift; |
| |
| /* |
| * There are 40 channels in this subdevice but only 32 are usable |
| * as DIO. The shift adjusts the mask/bits to account for the base |
| * channel in insn->chanspec. The state update can then be handled |
| * normally for the 32 usable channels. |
| */ |
| if (mask) { |
| s->state &= ~mask; |
| s->state |= (bits & mask); |
| ni_660x_write(dev, 0, s->state, NI660X_DIO32_OUTPUT); |
| } |
| |
| /* |
| * Return the input channels, shifted back to account for the base |
| * channel. |
| */ |
| data[1] = ni_660x_read(dev, 0, NI660X_DIO32_INPUT) >> shift; |
| |
| return insn->n; |
| } |
| |
| static void ni_660x_select_pfi_output(struct comedi_device *dev, |
| unsigned int chan, unsigned int out_sel) |
| { |
| const struct ni_660x_board *board = dev->board_ptr; |
| unsigned int active_chip = 0; |
| unsigned int idle_chip = 0; |
| unsigned int bits; |
| |
| if (chan >= NI_PFI(0)) |
| /* allow new and old names of pfi channels to work. */ |
| chan -= NI_PFI(0); |
| |
| if (board->n_chips > 1) { |
| if (out_sel == NI_660X_PFI_OUTPUT_COUNTER && |
| chan >= 8 && chan <= 23) { |
| /* counters 4-7 pfi channels */ |
| active_chip = 1; |
| idle_chip = 0; |
| } else { |
| /* counters 0-3 pfi channels */ |
| active_chip = 0; |
| idle_chip = 1; |
| } |
| } |
| |
| if (idle_chip != active_chip) { |
| /* set the pfi channel to high-z on the inactive chip */ |
| bits = ni_660x_read(dev, idle_chip, NI660X_IO_CFG(chan)); |
| bits &= ~NI660X_IO_CFG_OUT_SEL_MASK(chan); |
| bits |= NI660X_IO_CFG_OUT_SEL(chan, 0); /* high-z */ |
| ni_660x_write(dev, idle_chip, bits, NI660X_IO_CFG(chan)); |
| } |
| |
| /* set the pfi channel output on the active chip */ |
| bits = ni_660x_read(dev, active_chip, NI660X_IO_CFG(chan)); |
| bits &= ~NI660X_IO_CFG_OUT_SEL_MASK(chan); |
| bits |= NI660X_IO_CFG_OUT_SEL(chan, out_sel); |
| ni_660x_write(dev, active_chip, bits, NI660X_IO_CFG(chan)); |
| } |
| |
| static void ni_660x_set_pfi_direction(struct comedi_device *dev, |
| unsigned int chan, |
| unsigned int direction) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| u64 bit; |
| |
| if (chan >= NI_PFI(0)) |
| /* allow new and old names of pfi channels to work. */ |
| chan -= NI_PFI(0); |
| |
| bit = 1ULL << chan; |
| |
| if (direction == COMEDI_OUTPUT) { |
| devpriv->io_dir |= bit; |
| /* reset the output to currently assigned output value */ |
| ni_660x_select_pfi_output(dev, chan, devpriv->io_cfg[chan]); |
| } else { |
| devpriv->io_dir &= ~bit; |
| /* set pin to high-z; do not change currently assigned route */ |
| ni_660x_select_pfi_output(dev, chan, 0); |
| } |
| } |
| |
| static unsigned int ni_660x_get_pfi_direction(struct comedi_device *dev, |
| unsigned int chan) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| u64 bit; |
| |
| if (chan >= NI_PFI(0)) |
| /* allow new and old names of pfi channels to work. */ |
| chan -= NI_PFI(0); |
| |
| bit = 1ULL << chan; |
| |
| return (devpriv->io_dir & bit) ? COMEDI_OUTPUT : COMEDI_INPUT; |
| } |
| |
| static int ni_660x_set_pfi_routing(struct comedi_device *dev, |
| unsigned int chan, unsigned int source) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| |
| if (chan >= NI_PFI(0)) |
| /* allow new and old names of pfi channels to work. */ |
| chan -= NI_PFI(0); |
| |
| switch (source) { |
| case NI_660X_PFI_OUTPUT_COUNTER: |
| if (chan < 8) |
| return -EINVAL; |
| break; |
| case NI_660X_PFI_OUTPUT_DIO: |
| if (chan > 31) |
| return -EINVAL; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| devpriv->io_cfg[chan] = source; |
| if (ni_660x_get_pfi_direction(dev, chan) == COMEDI_OUTPUT) |
| ni_660x_select_pfi_output(dev, chan, devpriv->io_cfg[chan]); |
| return 0; |
| } |
| |
| static int ni_660x_get_pfi_routing(struct comedi_device *dev, unsigned int chan) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| |
| if (chan >= NI_PFI(0)) |
| /* allow new and old names of pfi channels to work. */ |
| chan -= NI_PFI(0); |
| |
| return devpriv->io_cfg[chan]; |
| } |
| |
| static void ni_660x_set_pfi_filter(struct comedi_device *dev, |
| unsigned int chan, unsigned int value) |
| { |
| unsigned int val; |
| |
| if (chan >= NI_PFI(0)) |
| /* allow new and old names of pfi channels to work. */ |
| chan -= NI_PFI(0); |
| |
| val = ni_660x_read(dev, 0, NI660X_IO_CFG(chan)); |
| val &= ~NI660X_IO_CFG_IN_SEL_MASK(chan); |
| val |= NI660X_IO_CFG_IN_SEL(chan, value); |
| ni_660x_write(dev, 0, val, NI660X_IO_CFG(chan)); |
| } |
| |
| static int ni_660x_dio_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| int ret; |
| |
| switch (data[0]) { |
| case INSN_CONFIG_DIO_OUTPUT: |
| ni_660x_set_pfi_direction(dev, chan, COMEDI_OUTPUT); |
| break; |
| |
| case INSN_CONFIG_DIO_INPUT: |
| ni_660x_set_pfi_direction(dev, chan, COMEDI_INPUT); |
| break; |
| |
| case INSN_CONFIG_DIO_QUERY: |
| data[1] = ni_660x_get_pfi_direction(dev, chan); |
| 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] = ni_660x_get_pfi_routing(dev, chan); |
| break; |
| |
| case INSN_CONFIG_FILTER: |
| ni_660x_set_pfi_filter(dev, chan, data[1]); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| return insn->n; |
| } |
| |
| static unsigned int _ni_get_valid_routes(struct comedi_device *dev, |
| unsigned int n_pairs, |
| unsigned int *pair_data) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| |
| return ni_get_valid_routes(&devpriv->routing_tables, n_pairs, |
| pair_data); |
| } |
| |
| /* |
| * Retrieves the current source of the output selector for the given |
| * destination. If the terminal for the destination is not already configured |
| * as an output, this function returns -EINVAL as error. |
| * |
| * Return: The register value of the destination output selector; |
| * -EINVAL if terminal is not configured for output. |
| */ |
| static inline int get_output_select_source(int dest, struct comedi_device *dev) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| int reg = -1; |
| |
| if (channel_is_pfi(dest)) { |
| if (ni_660x_get_pfi_direction(dev, dest) == COMEDI_OUTPUT) |
| reg = ni_660x_get_pfi_routing(dev, dest); |
| } else if (channel_is_rtsi(dest)) { |
| dev_dbg(dev->class_dev, |
| "%s: unhandled rtsi destination (%d) queried\n", |
| __func__, dest); |
| /* |
| * The following can be enabled when RTSI routing info is |
| * determined (not currently documented): |
| * if (ni_get_rtsi_direction(dev, dest) == COMEDI_OUTPUT) { |
| * reg = ni_get_rtsi_routing(dev, dest); |
| |
| * if (reg == NI_RTSI_OUTPUT_RGOUT0) { |
| * dest = NI_RGOUT0; ** prepare for lookup below ** |
| * reg = get_rgout0_reg(dev); |
| * } else if (reg >= NI_RTSI_OUTPUT_RTSI_BRD(0) && |
| * reg <= NI_RTSI_OUTPUT_RTSI_BRD(3)) { |
| * const int i = reg - NI_RTSI_OUTPUT_RTSI_BRD(0); |
| |
| * dest = NI_RTSI_BRD(i); ** prepare for lookup ** |
| * reg = get_ith_rtsi_brd_reg(i, dev); |
| * } |
| * } |
| */ |
| } else if (channel_is_ctr(dest)) { |
| reg = ni_tio_get_routing(devpriv->counter_dev, dest); |
| } else { |
| dev_dbg(dev->class_dev, |
| "%s: unhandled destination (%d) queried\n", |
| __func__, dest); |
| } |
| |
| if (reg >= 0) |
| return ni_find_route_source(CR_CHAN(reg), dest, |
| &devpriv->routing_tables); |
| return -EINVAL; |
| } |
| |
| /* |
| * Test a route: |
| * |
| * Return: -1 if not connectible; |
| * 0 if connectible and not connected; |
| * 1 if connectible and connected. |
| */ |
| static inline int test_route(unsigned int src, unsigned int dest, |
| struct comedi_device *dev) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| s8 reg = ni_route_to_register(CR_CHAN(src), dest, |
| &devpriv->routing_tables); |
| |
| if (reg < 0) |
| return -1; |
| if (get_output_select_source(dest, dev) != CR_CHAN(src)) |
| return 0; |
| return 1; |
| } |
| |
| /* Connect the actual route. */ |
| static inline int connect_route(unsigned int src, unsigned int dest, |
| struct comedi_device *dev) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| s8 reg = ni_route_to_register(CR_CHAN(src), dest, |
| &devpriv->routing_tables); |
| s8 current_src; |
| |
| if (reg < 0) |
| /* route is not valid */ |
| return -EINVAL; |
| |
| current_src = get_output_select_source(dest, dev); |
| if (current_src == CR_CHAN(src)) |
| return -EALREADY; |
| if (current_src >= 0) |
| /* destination mux is already busy. complain, don't overwrite */ |
| return -EBUSY; |
| |
| /* The route is valid and available. Now connect... */ |
| if (channel_is_pfi(CR_CHAN(dest))) { |
| /* |
| * set routing and then direction so that the output does not |
| * first get generated with the wrong pin |
| */ |
| ni_660x_set_pfi_routing(dev, dest, reg); |
| ni_660x_set_pfi_direction(dev, dest, COMEDI_OUTPUT); |
| } else if (channel_is_rtsi(CR_CHAN(dest))) { |
| dev_dbg(dev->class_dev, "%s: unhandled rtsi destination (%d)\n", |
| __func__, dest); |
| return -EINVAL; |
| /* |
| * The following can be enabled when RTSI routing info is |
| * determined (not currently documented): |
| * if (reg == NI_RTSI_OUTPUT_RGOUT0) { |
| * int ret = incr_rgout0_src_use(src, dev); |
| |
| * if (ret < 0) |
| * return ret; |
| * } else if (ni_rtsi_route_requires_mux(reg)) { |
| * ** Attempt to allocate and route (src->brd) ** |
| * int brd = incr_rtsi_brd_src_use(src, dev); |
| |
| * if (brd < 0) |
| * return brd; |
| |
| * ** Now lookup the register value for (brd->dest) ** |
| * reg = ni_lookup_route_register(brd, CR_CHAN(dest), |
| * &devpriv->routing_tables); |
| * } |
| |
| * ni_set_rtsi_direction(dev, dest, COMEDI_OUTPUT); |
| * ni_set_rtsi_routing(dev, dest, reg); |
| */ |
| } else if (channel_is_ctr(CR_CHAN(dest))) { |
| /* |
| * we are adding back the channel modifier info to set |
| * invert/edge info passed by the user |
| */ |
| ni_tio_set_routing(devpriv->counter_dev, dest, |
| reg | (src & ~CR_CHAN(-1))); |
| } else { |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static inline int disconnect_route(unsigned int src, unsigned int dest, |
| struct comedi_device *dev) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| s8 reg = ni_route_to_register(CR_CHAN(src), CR_CHAN(dest), |
| &devpriv->routing_tables); |
| |
| if (reg < 0) |
| /* route is not valid */ |
| return -EINVAL; |
| if (get_output_select_source(dest, dev) != CR_CHAN(src)) |
| /* cannot disconnect something not connected */ |
| return -EINVAL; |
| |
| /* The route is valid and is connected. Now disconnect... */ |
| if (channel_is_pfi(CR_CHAN(dest))) { |
| unsigned int source = ((CR_CHAN(dest) - NI_PFI(0)) < 8) |
| ? NI_660X_PFI_OUTPUT_DIO |
| : NI_660X_PFI_OUTPUT_COUNTER; |
| |
| /* set the pfi to high impedance, and disconnect */ |
| ni_660x_set_pfi_direction(dev, dest, COMEDI_INPUT); |
| ni_660x_set_pfi_routing(dev, dest, source); |
| } else if (channel_is_rtsi(CR_CHAN(dest))) { |
| dev_dbg(dev->class_dev, "%s: unhandled rtsi destination (%d)\n", |
| __func__, dest); |
| return -EINVAL; |
| /* |
| * The following can be enabled when RTSI routing info is |
| * determined (not currently documented): |
| * if (reg == NI_RTSI_OUTPUT_RGOUT0) { |
| * int ret = decr_rgout0_src_use(src, dev); |
| |
| * if (ret < 0) |
| * return ret; |
| * } else if (ni_rtsi_route_requires_mux(reg)) { |
| * ** find which RTSI_BRD line is source for rtsi pin ** |
| * int brd = ni_find_route_source( |
| * ni_get_rtsi_routing(dev, dest), CR_CHAN(dest), |
| * &devpriv->routing_tables); |
| |
| * if (brd < 0) |
| * return brd; |
| |
| * ** decrement/disconnect RTSI_BRD line from source ** |
| * decr_rtsi_brd_src_use(src, brd, dev); |
| * } |
| |
| * ** set rtsi output selector to default state ** |
| * reg = default_rtsi_routing[CR_CHAN(dest) - TRIGGER_LINE(0)]; |
| * ni_set_rtsi_direction(dev, dest, COMEDI_INPUT); |
| * ni_set_rtsi_routing(dev, dest, reg); |
| */ |
| } else if (channel_is_ctr(CR_CHAN(dest))) { |
| ni_tio_unset_routing(devpriv->counter_dev, dest); |
| } else { |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int ni_global_insn_config(struct comedi_device *dev, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| switch (data[0]) { |
| case INSN_DEVICE_CONFIG_TEST_ROUTE: |
| data[0] = test_route(data[1], data[2], dev); |
| return 2; |
| case INSN_DEVICE_CONFIG_CONNECT_ROUTE: |
| return connect_route(data[1], data[2], dev); |
| case INSN_DEVICE_CONFIG_DISCONNECT_ROUTE: |
| return disconnect_route(data[1], data[2], dev); |
| /* |
| * This case is already handled one level up. |
| * case INSN_DEVICE_CONFIG_GET_ROUTES: |
| */ |
| default: |
| return -EINVAL; |
| } |
| return 1; |
| } |
| |
| static void ni_660x_init_tio_chips(struct comedi_device *dev, |
| unsigned int n_chips) |
| { |
| struct ni_660x_private *devpriv = dev->private; |
| unsigned int chip; |
| unsigned int chan; |
| |
| /* |
| * We use the ioconfig registers to control dio direction, so zero |
| * output enables in stc dio control reg. |
| */ |
| ni_660x_write(dev, 0, 0, NI660X_STC_DIO_CONTROL); |
| |
| for (chip = 0; chip < n_chips; ++chip) { |
| /* init dma configuration register */ |
| devpriv->dma_cfg[chip] = 0; |
| for (chan = 0; chan < NI660X_MAX_DMA_CHANNEL; ++chan) |
| devpriv->dma_cfg[chip] |= NI660X_DMA_CFG_SEL_NONE(chan); |
| ni_660x_write(dev, chip, devpriv->dma_cfg[chip], |
| NI660X_DMA_CFG); |
| |
| /* init ioconfig registers */ |
| for (chan = 0; chan < NI660X_NUM_PFI_CHANNELS; ++chan) |
| ni_660x_write(dev, chip, 0, NI660X_IO_CFG(chan)); |
| } |
| } |
| |
| 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; |
| struct ni_gpct_device *gpct_dev; |
| unsigned int n_counters; |
| int subdev; |
| int ret; |
| unsigned int i; |
| unsigned int 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_attach(dev, true); /* use win1 */ |
| if (!devpriv->mite) |
| return -ENOMEM; |
| |
| ret = ni_660x_alloc_mite_rings(dev); |
| if (ret < 0) |
| return ret; |
| |
| ni_660x_init_tio_chips(dev, board->n_chips); |
| |
| /* prepare the device for globally-named routes. */ |
| if (ni_assign_device_routes("ni_660x", board->name, NULL, |
| &devpriv->routing_tables) < 0) { |
| dev_warn(dev->class_dev, "%s: %s device has no signal routing table.\n", |
| __func__, board->name); |
| dev_warn(dev->class_dev, "%s: High level NI signal names will not be available for this %s board.\n", |
| __func__, board->name); |
| } else { |
| /* |
| * only(?) assign insn_device_config if we have global names for |
| * this device. |
| */ |
| dev->insn_device_config = ni_global_insn_config; |
| dev->get_valid_routes = _ni_get_valid_routes; |
| } |
| |
| n_counters = board->n_chips * NI660X_COUNTERS_PER_CHIP; |
| gpct_dev = ni_gpct_device_construct(dev, |
| ni_660x_gpct_write, |
| ni_660x_gpct_read, |
| ni_gpct_variant_660x, |
| n_counters, |
| NI660X_COUNTERS_PER_CHIP, |
| &devpriv->routing_tables); |
| if (!gpct_dev) |
| return -ENOMEM; |
| devpriv->counter_dev = gpct_dev; |
| |
| ret = comedi_alloc_subdevices(dev, 2 + NI660X_MAX_COUNTERS); |
| if (ret) |
| return ret; |
| |
| subdev = 0; |
| |
| s = &dev->subdevices[subdev++]; |
| /* Old GENERAL-PURPOSE COUNTER/TIME (GPCT) subdevice, no longer used */ |
| s->type = COMEDI_SUBD_UNUSED; |
| |
| /* |
| * Digital I/O subdevice |
| * |
| * There are 40 channels but only the first 32 can be digital I/Os. |
| * The last 8 are dedicated to counters 0 and 1. |
| * |
| * Counter 0-3 signals are from the first TIO chip. |
| * Counter 4-7 signals are from the second TIO chip. |
| * |
| * Comedi External |
| * PFI Chan DIO Chan Counter Signal |
| * ------- -------- -------------- |
| * 0 0 |
| * 1 1 |
| * 2 2 |
| * 3 3 |
| * 4 4 |
| * 5 5 |
| * 6 6 |
| * 7 7 |
| * 8 8 CTR 7 OUT |
| * 9 9 CTR 7 AUX |
| * 10 10 CTR 7 GATE |
| * 11 11 CTR 7 SOURCE |
| * 12 12 CTR 6 OUT |
| * 13 13 CTR 6 AUX |
| * 14 14 CTR 6 GATE |
| * 15 15 CTR 6 SOURCE |
| * 16 16 CTR 5 OUT |
| * 17 17 CTR 5 AUX |
| * 18 18 CTR 5 GATE |
| * 19 19 CTR 5 SOURCE |
| * 20 20 CTR 4 OUT |
| * 21 21 CTR 4 AUX |
| * 22 22 CTR 4 GATE |
| * 23 23 CTR 4 SOURCE |
| * 24 24 CTR 3 OUT |
| * 25 25 CTR 3 AUX |
| * 26 26 CTR 3 GATE |
| * 27 27 CTR 3 SOURCE |
| * 28 28 CTR 2 OUT |
| * 29 29 CTR 2 AUX |
| * 30 30 CTR 2 GATE |
| * 31 31 CTR 2 SOURCE |
| * 32 CTR 1 OUT |
| * 33 CTR 1 AUX |
| * 34 CTR 1 GATE |
| * 35 CTR 1 SOURCE |
| * 36 CTR 0 OUT |
| * 37 CTR 0 AUX |
| * 38 CTR 0 GATE |
| * 39 CTR 0 SOURCE |
| */ |
| s = &dev->subdevices[subdev++]; |
| s->type = COMEDI_SUBD_DIO; |
| s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
| s->n_chan = NI660X_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; |
| |
| /* |
| * Default the DIO channels as: |
| * chan 0-7: DIO inputs |
| * chan 8-39: counter signal inputs |
| */ |
| for (i = 0; i < s->n_chan; ++i) { |
| unsigned int source = (i < 8) ? NI_660X_PFI_OUTPUT_DIO |
| : NI_660X_PFI_OUTPUT_COUNTER; |
| |
| ni_660x_set_pfi_routing(dev, i, source); |
| ni_660x_set_pfi_direction(dev, i, COMEDI_INPUT);/* high-z */ |
| } |
| |
| /* Counter subdevices (4 NI TIO General Purpose Counters per chip) */ |
| for (i = 0; i < NI660X_MAX_COUNTERS; ++i) { |
| s = &dev->subdevices[subdev++]; |
| if (i < n_counters) { |
| struct ni_gpct *counter = &gpct_dev->counters[i]; |
| |
| 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->len_chanlist = 1; |
| s->do_cmd = ni_660x_cmd; |
| s->do_cmdtest = ni_tio_cmdtest; |
| s->cancel = ni_660x_cancel; |
| s->poll = ni_660x_input_poll; |
| s->buf_change = ni_660x_buf_change; |
| s->async_dma_dir = DMA_BIDIRECTIONAL; |
| s->private = counter; |
| |
| ni_tio_init_counter(counter); |
| } else { |
| s->type = COMEDI_SUBD_UNUSED; |
| } |
| } |
| |
| /* |
| * 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 = NI660X_GLOBAL_INT_GLOBAL; |
| if (board->n_chips > 1) |
| global_interrupt_config_bits |= NI660X_GLOBAL_INT_CASCADE; |
| ni_660x_write(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) { |
| ni_660x_write(dev, 0, 0, NI660X_GLOBAL_INT_CFG); |
| free_irq(dev->irq, dev); |
| } |
| if (devpriv) { |
| 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, 0x2db0), BOARD_PCI6608 }, |
| { PCI_VDEVICE(NI, 0x2cc0), BOARD_PXI6608 }, |
| { PCI_VDEVICE(NI, 0x1e30), BOARD_PCI6624 }, |
| { 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 https://www.comedi.org"); |
| MODULE_DESCRIPTION("Comedi driver for NI 660x counter/timer boards"); |
| MODULE_LICENSE("GPL"); |