// SPDX-License-Identifier: GPL-2.0+ /* * ni_65xx.c * Comedi driver for National Instruments PCI-65xx static dio boards * * Copyright (C) 2006 Jon Grierson * Copyright (C) 2006 Frank Mori Hess * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 1999,2002,2003 David A. Schleef */ /* * Driver: ni_65xx * Description: National Instruments 65xx static dio boards * Author: Jon Grierson , * Frank Mori Hess * Status: testing * Devices: [National Instruments] PCI-6509 (pci-6509), PXI-6509 (pxi-6509), * PCI-6510 (pci-6510), PCI-6511 (pci-6511), PXI-6511 (pxi-6511), * PCI-6512 (pci-6512), PXI-6512 (pxi-6512), PCI-6513 (pci-6513), * PXI-6513 (pxi-6513), PCI-6514 (pci-6514), PXI-6514 (pxi-6514), * PCI-6515 (pxi-6515), PXI-6515 (pxi-6515), PCI-6516 (pci-6516), * PCI-6517 (pci-6517), PCI-6518 (pci-6518), PCI-6519 (pci-6519), * PCI-6520 (pci-6520), PCI-6521 (pci-6521), PXI-6521 (pxi-6521), * PCI-6528 (pci-6528), PXI-6528 (pxi-6528) * Updated: Mon, 21 Jul 2014 12:49:58 +0000 * * Configuration Options: not applicable, uses PCI auto config * * Based on the PCI-6527 driver by ds. * The interrupt subdevice (subdevice 3) is probably broken for all * boards except maybe the 6514. * * This driver previously inverted the outputs on PCI-6513 through to * PCI-6519 and on PXI-6513 through to PXI-6515. It no longer inverts * outputs on those cards by default as it didn't make much sense. If * you require the outputs to be inverted on those cards for legacy * reasons, set the module parameter "legacy_invert_outputs=true" when * loading the module, or set "ni_65xx.legacy_invert_outputs=true" on * the kernel command line if the driver is built in to the kernel. */ /* * Manuals (available from ftp://ftp.natinst.com/support/manuals) * * 370106b.pdf 6514 Register Level Programmer Manual */ #include #include #include "../comedi_pci.h" /* * PCI BAR1 Register Map */ /* Non-recurring Registers (8-bit except where noted) */ #define NI_65XX_ID_REG 0x00 #define NI_65XX_CLR_REG 0x01 #define NI_65XX_CLR_WDOG_INT BIT(6) #define NI_65XX_CLR_WDOG_PING BIT(5) #define NI_65XX_CLR_WDOG_EXP BIT(4) #define NI_65XX_CLR_EDGE_INT BIT(3) #define NI_65XX_CLR_OVERFLOW_INT BIT(2) #define NI_65XX_STATUS_REG 0x02 #define NI_65XX_STATUS_WDOG_INT BIT(5) #define NI_65XX_STATUS_FALL_EDGE BIT(4) #define NI_65XX_STATUS_RISE_EDGE BIT(3) #define NI_65XX_STATUS_INT BIT(2) #define NI_65XX_STATUS_OVERFLOW_INT BIT(1) #define NI_65XX_STATUS_EDGE_INT BIT(0) #define NI_65XX_CTRL_REG 0x03 #define NI_65XX_CTRL_WDOG_ENA BIT(5) #define NI_65XX_CTRL_FALL_EDGE_ENA BIT(4) #define NI_65XX_CTRL_RISE_EDGE_ENA BIT(3) #define NI_65XX_CTRL_INT_ENA BIT(2) #define NI_65XX_CTRL_OVERFLOW_ENA BIT(1) #define NI_65XX_CTRL_EDGE_ENA BIT(0) #define NI_65XX_REV_REG 0x04 /* 32-bit */ #define NI_65XX_FILTER_REG 0x08 /* 32-bit */ #define NI_65XX_RTSI_ROUTE_REG 0x0c /* 16-bit */ #define NI_65XX_RTSI_EDGE_REG 0x0e /* 16-bit */ #define NI_65XX_RTSI_WDOG_REG 0x10 /* 16-bit */ #define NI_65XX_RTSI_TRIG_REG 0x12 /* 16-bit */ #define NI_65XX_AUTO_CLK_SEL_REG 0x14 /* PXI-6528 only */ #define NI_65XX_AUTO_CLK_SEL_STATUS BIT(1) #define NI_65XX_AUTO_CLK_SEL_DISABLE BIT(0) #define NI_65XX_WDOG_CTRL_REG 0x15 #define NI_65XX_WDOG_CTRL_ENA BIT(0) #define NI_65XX_RTSI_CFG_REG 0x16 #define NI_65XX_RTSI_CFG_RISE_SENSE BIT(2) #define NI_65XX_RTSI_CFG_FALL_SENSE BIT(1) #define NI_65XX_RTSI_CFG_SYNC_DETECT BIT(0) #define NI_65XX_WDOG_STATUS_REG 0x17 #define NI_65XX_WDOG_STATUS_EXP BIT(0) #define NI_65XX_WDOG_INTERVAL_REG 0x18 /* 32-bit */ /* Recurring port registers (8-bit) */ #define NI_65XX_PORT(x) ((x) * 0x10) #define NI_65XX_IO_DATA_REG(x) (0x40 + NI_65XX_PORT(x)) #define NI_65XX_IO_SEL_REG(x) (0x41 + NI_65XX_PORT(x)) #define NI_65XX_IO_SEL_OUTPUT 0 #define NI_65XX_IO_SEL_INPUT BIT(0) #define NI_65XX_RISE_EDGE_ENA_REG(x) (0x42 + NI_65XX_PORT(x)) #define NI_65XX_FALL_EDGE_ENA_REG(x) (0x43 + NI_65XX_PORT(x)) #define NI_65XX_FILTER_ENA(x) (0x44 + NI_65XX_PORT(x)) #define NI_65XX_WDOG_HIZ_REG(x) (0x46 + NI_65XX_PORT(x)) #define NI_65XX_WDOG_ENA(x) (0x47 + NI_65XX_PORT(x)) #define NI_65XX_WDOG_HI_LO_REG(x) (0x48 + NI_65XX_PORT(x)) #define NI_65XX_RTSI_ENA(x) (0x49 + NI_65XX_PORT(x)) #define NI_65XX_PORT_TO_CHAN(x) ((x) * 8) #define NI_65XX_CHAN_TO_PORT(x) ((x) / 8) #define NI_65XX_CHAN_TO_MASK(x) (1 << ((x) % 8)) enum ni_65xx_boardid { BOARD_PCI6509, BOARD_PXI6509, BOARD_PCI6510, BOARD_PCI6511, BOARD_PXI6511, BOARD_PCI6512, BOARD_PXI6512, BOARD_PCI6513, BOARD_PXI6513, BOARD_PCI6514, BOARD_PXI6514, BOARD_PCI6515, BOARD_PXI6515, BOARD_PCI6516, BOARD_PCI6517, BOARD_PCI6518, BOARD_PCI6519, BOARD_PCI6520, BOARD_PCI6521, BOARD_PXI6521, BOARD_PCI6528, BOARD_PXI6528, }; struct ni_65xx_board { const char *name; unsigned int num_dio_ports; unsigned int num_di_ports; unsigned int num_do_ports; unsigned int legacy_invert:1; }; static const struct ni_65xx_board ni_65xx_boards[] = { [BOARD_PCI6509] = { .name = "pci-6509", .num_dio_ports = 12, }, [BOARD_PXI6509] = { .name = "pxi-6509", .num_dio_ports = 12, }, [BOARD_PCI6510] = { .name = "pci-6510", .num_di_ports = 4, }, [BOARD_PCI6511] = { .name = "pci-6511", .num_di_ports = 8, }, [BOARD_PXI6511] = { .name = "pxi-6511", .num_di_ports = 8, }, [BOARD_PCI6512] = { .name = "pci-6512", .num_do_ports = 8, }, [BOARD_PXI6512] = { .name = "pxi-6512", .num_do_ports = 8, }, [BOARD_PCI6513] = { .name = "pci-6513", .num_do_ports = 8, .legacy_invert = 1, }, [BOARD_PXI6513] = { .name = "pxi-6513", .num_do_ports = 8, .legacy_invert = 1, }, [BOARD_PCI6514] = { .name = "pci-6514", .num_di_ports = 4, .num_do_ports = 4, .legacy_invert = 1, }, [BOARD_PXI6514] = { .name = "pxi-6514", .num_di_ports = 4, .num_do_ports = 4, .legacy_invert = 1, }, [BOARD_PCI6515] = { .name = "pci-6515", .num_di_ports = 4, .num_do_ports = 4, .legacy_invert = 1, }, [BOARD_PXI6515] = { .name = "pxi-6515", .num_di_ports = 4, .num_do_ports = 4, .legacy_invert = 1, }, [BOARD_PCI6516] = { .name = "pci-6516", .num_do_ports = 4, .legacy_invert = 1, }, [BOARD_PCI6517] = { .name = "pci-6517", .num_do_ports = 4, .legacy_invert = 1, }, [BOARD_PCI6518] = { .name = "pci-6518", .num_di_ports = 2, .num_do_ports = 2, .legacy_invert = 1, }, [BOARD_PCI6519] = { .name = "pci-6519", .num_di_ports = 2, .num_do_ports = 2, .legacy_invert = 1, }, [BOARD_PCI6520] = { .name = "pci-6520", .num_di_ports = 1, .num_do_ports = 1, }, [BOARD_PCI6521] = { .name = "pci-6521", .num_di_ports = 1, .num_do_ports = 1, }, [BOARD_PXI6521] = { .name = "pxi-6521", .num_di_ports = 1, .num_do_ports = 1, }, [BOARD_PCI6528] = { .name = "pci-6528", .num_di_ports = 3, .num_do_ports = 3, }, [BOARD_PXI6528] = { .name = "pxi-6528", .num_di_ports = 3, .num_do_ports = 3, }, }; static bool ni_65xx_legacy_invert_outputs; module_param_named(legacy_invert_outputs, ni_65xx_legacy_invert_outputs, bool, 0444); MODULE_PARM_DESC(legacy_invert_outputs, "invert outputs of PCI/PXI-6513/6514/6515/6516/6517/6518/6519 for compatibility with old user code"); static unsigned int ni_65xx_num_ports(struct comedi_device *dev) { const struct ni_65xx_board *board = dev->board_ptr; return board->num_dio_ports + board->num_di_ports + board->num_do_ports; } static void ni_65xx_disable_input_filters(struct comedi_device *dev) { unsigned int num_ports = ni_65xx_num_ports(dev); int i; /* disable input filtering on all ports */ for (i = 0; i < num_ports; ++i) writeb(0x00, dev->mmio + NI_65XX_FILTER_ENA(i)); /* set filter interval to 0 (32bit reg) */ writel(0x00000000, dev->mmio + NI_65XX_FILTER_REG); } /* updates edge detection for base_chan to base_chan+31 */ static void ni_65xx_update_edge_detection(struct comedi_device *dev, unsigned int base_chan, unsigned int rising, unsigned int falling) { unsigned int num_ports = ni_65xx_num_ports(dev); unsigned int port; if (base_chan >= NI_65XX_PORT_TO_CHAN(num_ports)) return; for (port = NI_65XX_CHAN_TO_PORT(base_chan); port < num_ports; port++) { int bitshift = (int)(NI_65XX_PORT_TO_CHAN(port) - base_chan); unsigned int port_mask, port_rising, port_falling; if (bitshift >= 32) break; if (bitshift >= 0) { port_mask = ~0U >> bitshift; port_rising = rising >> bitshift; port_falling = falling >> bitshift; } else { port_mask = ~0U << -bitshift; port_rising = rising << -bitshift; port_falling = falling << -bitshift; } if (port_mask & 0xff) { if (~port_mask & 0xff) { port_rising |= readb(dev->mmio + NI_65XX_RISE_EDGE_ENA_REG(port)) & ~port_mask; port_falling |= readb(dev->mmio + NI_65XX_FALL_EDGE_ENA_REG(port)) & ~port_mask; } writeb(port_rising & 0xff, dev->mmio + NI_65XX_RISE_EDGE_ENA_REG(port)); writeb(port_falling & 0xff, dev->mmio + NI_65XX_FALL_EDGE_ENA_REG(port)); } } } static void ni_65xx_disable_edge_detection(struct comedi_device *dev) { /* clear edge detection for channels 0 to 31 */ ni_65xx_update_edge_detection(dev, 0, 0, 0); /* clear edge detection for channels 32 to 63 */ ni_65xx_update_edge_detection(dev, 32, 0, 0); /* clear edge detection for channels 64 to 95 */ ni_65xx_update_edge_detection(dev, 64, 0, 0); } static int ni_65xx_dio_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { unsigned long base_port = (unsigned long)s->private; unsigned int chan = CR_CHAN(insn->chanspec); unsigned int chan_mask = NI_65XX_CHAN_TO_MASK(chan); unsigned int port = base_port + NI_65XX_CHAN_TO_PORT(chan); unsigned int interval; unsigned int val; switch (data[0]) { case INSN_CONFIG_FILTER: /* * The deglitch filter interval is specified in nanoseconds. * The hardware supports intervals in 200ns increments. Round * the user values up and return the actual interval. */ interval = (data[1] + 100) / 200; if (interval > 0xfffff) interval = 0xfffff; data[1] = interval * 200; /* * Enable/disable the channel for deglitch filtering. Note * that the filter interval is never set to '0'. This is done * because other channels might still be enabled for filtering. */ val = readb(dev->mmio + NI_65XX_FILTER_ENA(port)); if (interval) { writel(interval, dev->mmio + NI_65XX_FILTER_REG); val |= chan_mask; } else { val &= ~chan_mask; } writeb(val, dev->mmio + NI_65XX_FILTER_ENA(port)); break; case INSN_CONFIG_DIO_OUTPUT: if (s->type != COMEDI_SUBD_DIO) return -EINVAL; writeb(NI_65XX_IO_SEL_OUTPUT, dev->mmio + NI_65XX_IO_SEL_REG(port)); break; case INSN_CONFIG_DIO_INPUT: if (s->type != COMEDI_SUBD_DIO) return -EINVAL; writeb(NI_65XX_IO_SEL_INPUT, dev->mmio + NI_65XX_IO_SEL_REG(port)); break; case INSN_CONFIG_DIO_QUERY: if (s->type != COMEDI_SUBD_DIO) return -EINVAL; val = readb(dev->mmio + NI_65XX_IO_SEL_REG(port)); data[1] = (val == NI_65XX_IO_SEL_INPUT) ? COMEDI_INPUT : COMEDI_OUTPUT; break; default: return -EINVAL; } return insn->n; } static int ni_65xx_dio_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { unsigned long base_port = (unsigned long)s->private; unsigned int base_chan = CR_CHAN(insn->chanspec); int last_port_offset = NI_65XX_CHAN_TO_PORT(s->n_chan - 1); unsigned int read_bits = 0; int port_offset; for (port_offset = NI_65XX_CHAN_TO_PORT(base_chan); port_offset <= last_port_offset; port_offset++) { unsigned int port = base_port + port_offset; int base_port_channel = NI_65XX_PORT_TO_CHAN(port_offset); unsigned int port_mask, port_data, bits; int bitshift = base_port_channel - base_chan; if (bitshift >= 32) break; port_mask = data[0]; port_data = data[1]; if (bitshift > 0) { port_mask >>= bitshift; port_data >>= bitshift; } else { port_mask <<= -bitshift; port_data <<= -bitshift; } port_mask &= 0xff; port_data &= 0xff; /* update the outputs */ if (port_mask) { bits = readb(dev->mmio + NI_65XX_IO_DATA_REG(port)); bits ^= s->io_bits; /* invert if necessary */ bits &= ~port_mask; bits |= (port_data & port_mask); bits ^= s->io_bits; /* invert back */ writeb(bits, dev->mmio + NI_65XX_IO_DATA_REG(port)); } /* read back the actual state */ bits = readb(dev->mmio + NI_65XX_IO_DATA_REG(port)); bits ^= s->io_bits; /* invert if necessary */ if (bitshift > 0) bits <<= bitshift; else bits >>= -bitshift; read_bits |= bits; } data[1] = read_bits; return insn->n; } static irqreturn_t ni_65xx_interrupt(int irq, void *d) { struct comedi_device *dev = d; struct comedi_subdevice *s = dev->read_subdev; unsigned int status; status = readb(dev->mmio + NI_65XX_STATUS_REG); if ((status & NI_65XX_STATUS_INT) == 0) return IRQ_NONE; if ((status & NI_65XX_STATUS_EDGE_INT) == 0) return IRQ_NONE; writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, dev->mmio + NI_65XX_CLR_REG); comedi_buf_write_samples(s, &s->state, 1); comedi_handle_events(dev, s); return IRQ_HANDLED; } static int ni_65xx_intr_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_cmd *cmd) { int err = 0; /* Step 1 : check if triggers are trivially valid */ err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW); err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER); err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW); err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT); if (err) return 1; /* Step 2a : make sure trigger sources are unique */ /* Step 2b : and mutually compatible */ /* Step 3: check if arguments are trivially valid */ err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0); err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0); err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0); err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0); if (err) return 3; /* Step 4: fix up any arguments */ /* Step 5: check channel list if it exists */ return 0; } static int ni_65xx_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) { writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, dev->mmio + NI_65XX_CLR_REG); writeb(NI_65XX_CTRL_FALL_EDGE_ENA | NI_65XX_CTRL_RISE_EDGE_ENA | NI_65XX_CTRL_INT_ENA | NI_65XX_CTRL_EDGE_ENA, dev->mmio + NI_65XX_CTRL_REG); return 0; } static int ni_65xx_intr_cancel(struct comedi_device *dev, struct comedi_subdevice *s) { writeb(0x00, dev->mmio + NI_65XX_CTRL_REG); return 0; } static int ni_65xx_intr_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { data[1] = 0; return insn->n; } static int ni_65xx_intr_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { switch (data[0]) { case INSN_CONFIG_CHANGE_NOTIFY: /* add instruction to check_insn_config_length() */ if (insn->n != 3) return -EINVAL; /* update edge detection for channels 0 to 31 */ ni_65xx_update_edge_detection(dev, 0, data[1], data[2]); /* clear edge detection for channels 32 to 63 */ ni_65xx_update_edge_detection(dev, 32, 0, 0); /* clear edge detection for channels 64 to 95 */ ni_65xx_update_edge_detection(dev, 64, 0, 0); break; case INSN_CONFIG_DIGITAL_TRIG: /* check trigger number */ if (data[1] != 0) return -EINVAL; /* check digital trigger operation */ switch (data[2]) { case COMEDI_DIGITAL_TRIG_DISABLE: ni_65xx_disable_edge_detection(dev); break; case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: /* * update edge detection for channels data[3] * to (data[3] + 31) */ ni_65xx_update_edge_detection(dev, data[3], data[4], data[5]); break; default: return -EINVAL; } break; default: return -EINVAL; } return insn->n; } /* ripped from mite.h and mite_setup2() to avoid mite dependency */ #define MITE_IODWBSR 0xc0 /* IO Device Window Base Size Register */ #define WENAB BIT(7) /* window enable */ static int ni_65xx_mite_init(struct pci_dev *pcidev) { void __iomem *mite_base; u32 main_phys_addr; /* ioremap the MITE registers (BAR 0) temporarily */ mite_base = pci_ioremap_bar(pcidev, 0); if (!mite_base) return -ENOMEM; /* set data window to main registers (BAR 1) */ main_phys_addr = pci_resource_start(pcidev, 1); writel(main_phys_addr | WENAB, mite_base + MITE_IODWBSR); /* finished with MITE registers */ iounmap(mite_base); return 0; } static int ni_65xx_auto_attach(struct comedi_device *dev, unsigned long context) { struct pci_dev *pcidev = comedi_to_pci_dev(dev); const struct ni_65xx_board *board = NULL; struct comedi_subdevice *s; unsigned int i; int ret; if (context < ARRAY_SIZE(ni_65xx_boards)) board = &ni_65xx_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_65xx_mite_init(pcidev); if (ret) return ret; dev->mmio = pci_ioremap_bar(pcidev, 1); if (!dev->mmio) return -ENOMEM; writeb(NI_65XX_CLR_EDGE_INT | NI_65XX_CLR_OVERFLOW_INT, dev->mmio + NI_65XX_CLR_REG); writeb(0x00, dev->mmio + NI_65XX_CTRL_REG); if (pcidev->irq) { ret = request_irq(pcidev->irq, ni_65xx_interrupt, IRQF_SHARED, dev->board_name, dev); if (ret == 0) dev->irq = pcidev->irq; } dev_info(dev->class_dev, "board: %s, ID=0x%02x", dev->board_name, readb(dev->mmio + NI_65XX_ID_REG)); ret = comedi_alloc_subdevices(dev, 4); if (ret) return ret; s = &dev->subdevices[0]; if (board->num_di_ports) { s->type = COMEDI_SUBD_DI; s->subdev_flags = SDF_READABLE; s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_di_ports); s->maxdata = 1; s->range_table = &range_digital; s->insn_bits = ni_65xx_dio_insn_bits; s->insn_config = ni_65xx_dio_insn_config; /* the input ports always start at port 0 */ s->private = (void *)0; } else { s->type = COMEDI_SUBD_UNUSED; } s = &dev->subdevices[1]; if (board->num_do_ports) { s->type = COMEDI_SUBD_DO; s->subdev_flags = SDF_WRITABLE; s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_do_ports); s->maxdata = 1; s->range_table = &range_digital; s->insn_bits = ni_65xx_dio_insn_bits; /* the output ports always start after the input ports */ s->private = (void *)(unsigned long)board->num_di_ports; /* * Use the io_bits to handle the inverted outputs. Inverted * outputs are only supported if the "legacy_invert_outputs" * module parameter is set to "true". */ if (ni_65xx_legacy_invert_outputs && board->legacy_invert) s->io_bits = 0xff; /* reset all output ports to comedi '0' */ for (i = 0; i < board->num_do_ports; ++i) { writeb(s->io_bits, /* inverted if necessary */ dev->mmio + NI_65XX_IO_DATA_REG(board->num_di_ports + i)); } } else { s->type = COMEDI_SUBD_UNUSED; } s = &dev->subdevices[2]; if (board->num_dio_ports) { s->type = COMEDI_SUBD_DIO; s->subdev_flags = SDF_READABLE | SDF_WRITABLE; s->n_chan = NI_65XX_PORT_TO_CHAN(board->num_dio_ports); s->maxdata = 1; s->range_table = &range_digital; s->insn_bits = ni_65xx_dio_insn_bits; s->insn_config = ni_65xx_dio_insn_config; /* the input/output ports always start at port 0 */ s->private = (void *)0; /* configure all ports for input */ for (i = 0; i < board->num_dio_ports; ++i) { writeb(NI_65XX_IO_SEL_INPUT, dev->mmio + NI_65XX_IO_SEL_REG(i)); } } else { s->type = COMEDI_SUBD_UNUSED; } s = &dev->subdevices[3]; s->type = COMEDI_SUBD_DI; s->subdev_flags = SDF_READABLE; s->n_chan = 1; s->maxdata = 1; s->range_table = &range_digital; s->insn_bits = ni_65xx_intr_insn_bits; if (dev->irq) { dev->read_subdev = s; s->subdev_flags |= SDF_CMD_READ; s->len_chanlist = 1; s->insn_config = ni_65xx_intr_insn_config; s->do_cmdtest = ni_65xx_intr_cmdtest; s->do_cmd = ni_65xx_intr_cmd; s->cancel = ni_65xx_intr_cancel; } ni_65xx_disable_input_filters(dev); ni_65xx_disable_edge_detection(dev); return 0; } static void ni_65xx_detach(struct comedi_device *dev) { if (dev->mmio) writeb(0x00, dev->mmio + NI_65XX_CTRL_REG); comedi_pci_detach(dev); } static struct comedi_driver ni_65xx_driver = { .driver_name = "ni_65xx", .module = THIS_MODULE, .auto_attach = ni_65xx_auto_attach, .detach = ni_65xx_detach, }; static int ni_65xx_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { return comedi_pci_auto_config(dev, &ni_65xx_driver, id->driver_data); } static const struct pci_device_id ni_65xx_pci_table[] = { { PCI_VDEVICE(NI, 0x1710), BOARD_PXI6509 }, { PCI_VDEVICE(NI, 0x7085), BOARD_PCI6509 }, { PCI_VDEVICE(NI, 0x7086), BOARD_PXI6528 }, { PCI_VDEVICE(NI, 0x7087), BOARD_PCI6515 }, { PCI_VDEVICE(NI, 0x7088), BOARD_PCI6514 }, { PCI_VDEVICE(NI, 0x70a9), BOARD_PCI6528 }, { PCI_VDEVICE(NI, 0x70c3), BOARD_PCI6511 }, { PCI_VDEVICE(NI, 0x70c8), BOARD_PCI6513 }, { PCI_VDEVICE(NI, 0x70c9), BOARD_PXI6515 }, { PCI_VDEVICE(NI, 0x70cc), BOARD_PCI6512 }, { PCI_VDEVICE(NI, 0x70cd), BOARD_PXI6514 }, { PCI_VDEVICE(NI, 0x70d1), BOARD_PXI6513 }, { PCI_VDEVICE(NI, 0x70d2), BOARD_PXI6512 }, { PCI_VDEVICE(NI, 0x70d3), BOARD_PXI6511 }, { PCI_VDEVICE(NI, 0x7124), BOARD_PCI6510 }, { PCI_VDEVICE(NI, 0x7125), BOARD_PCI6516 }, { PCI_VDEVICE(NI, 0x7126), BOARD_PCI6517 }, { PCI_VDEVICE(NI, 0x7127), BOARD_PCI6518 }, { PCI_VDEVICE(NI, 0x7128), BOARD_PCI6519 }, { PCI_VDEVICE(NI, 0x718b), BOARD_PCI6521 }, { PCI_VDEVICE(NI, 0x718c), BOARD_PXI6521 }, { PCI_VDEVICE(NI, 0x71c5), BOARD_PCI6520 }, { 0 } }; MODULE_DEVICE_TABLE(pci, ni_65xx_pci_table); static struct pci_driver ni_65xx_pci_driver = { .name = "ni_65xx", .id_table = ni_65xx_pci_table, .probe = ni_65xx_pci_probe, .remove = comedi_pci_auto_unconfig, }; module_comedi_pci_driver(ni_65xx_driver, ni_65xx_pci_driver); MODULE_AUTHOR("Comedi http://www.comedi.org"); MODULE_DESCRIPTION("Comedi driver for NI PCI-65xx static dio boards"); MODULE_LICENSE("GPL");