| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * ni_6527.c |
| * Comedi driver for National Instruments PCI-6527 |
| * |
| * COMEDI - Linux Control and Measurement Device Interface |
| * Copyright (C) 1999,2002,2003 David A. Schleef <ds@schleef.org> |
| */ |
| |
| /* |
| * Driver: ni_6527 |
| * Description: National Instruments 6527 |
| * Devices: [National Instruments] PCI-6527 (pci-6527), PXI-6527 (pxi-6527) |
| * Author: David A. Schleef <ds@schleef.org> |
| * Updated: Sat, 25 Jan 2003 13:24:40 -0800 |
| * Status: works |
| * |
| * Configuration Options: not applicable, uses PCI auto config |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| |
| #include "../comedi_pci.h" |
| |
| /* |
| * PCI BAR1 - Register memory map |
| * |
| * Manuals (available from ftp://ftp.natinst.com/support/manuals) |
| * 370106b.pdf 6527 Register Level Programmer Manual |
| */ |
| #define NI6527_DI_REG(x) (0x00 + (x)) |
| #define NI6527_DO_REG(x) (0x03 + (x)) |
| #define NI6527_ID_REG 0x06 |
| #define NI6527_CLR_REG 0x07 |
| #define NI6527_CLR_EDGE BIT(3) |
| #define NI6527_CLR_OVERFLOW BIT(2) |
| #define NI6527_CLR_FILT BIT(1) |
| #define NI6527_CLR_INTERVAL BIT(0) |
| #define NI6527_CLR_IRQS (NI6527_CLR_EDGE | NI6527_CLR_OVERFLOW) |
| #define NI6527_CLR_RESET_FILT (NI6527_CLR_FILT | NI6527_CLR_INTERVAL) |
| #define NI6527_FILT_INTERVAL_REG(x) (0x08 + (x)) |
| #define NI6527_FILT_ENA_REG(x) (0x0c + (x)) |
| #define NI6527_STATUS_REG 0x14 |
| #define NI6527_STATUS_IRQ BIT(2) |
| #define NI6527_STATUS_OVERFLOW BIT(1) |
| #define NI6527_STATUS_EDGE BIT(0) |
| #define NI6527_CTRL_REG 0x15 |
| #define NI6527_CTRL_FALLING BIT(4) |
| #define NI6527_CTRL_RISING BIT(3) |
| #define NI6527_CTRL_IRQ BIT(2) |
| #define NI6527_CTRL_OVERFLOW BIT(1) |
| #define NI6527_CTRL_EDGE BIT(0) |
| #define NI6527_CTRL_DISABLE_IRQS 0 |
| #define NI6527_CTRL_ENABLE_IRQS (NI6527_CTRL_FALLING | \ |
| NI6527_CTRL_RISING | \ |
| NI6527_CTRL_IRQ | NI6527_CTRL_EDGE) |
| #define NI6527_RISING_EDGE_REG(x) (0x18 + (x)) |
| #define NI6527_FALLING_EDGE_REG(x) (0x20 + (x)) |
| |
| enum ni6527_boardid { |
| BOARD_PCI6527, |
| BOARD_PXI6527, |
| }; |
| |
| struct ni6527_board { |
| const char *name; |
| }; |
| |
| static const struct ni6527_board ni6527_boards[] = { |
| [BOARD_PCI6527] = { |
| .name = "pci-6527", |
| }, |
| [BOARD_PXI6527] = { |
| .name = "pxi-6527", |
| }, |
| }; |
| |
| struct ni6527_private { |
| unsigned int filter_interval; |
| unsigned int filter_enable; |
| }; |
| |
| static void ni6527_set_filter_interval(struct comedi_device *dev, |
| unsigned int val) |
| { |
| struct ni6527_private *devpriv = dev->private; |
| |
| if (val != devpriv->filter_interval) { |
| writeb(val & 0xff, dev->mmio + NI6527_FILT_INTERVAL_REG(0)); |
| writeb((val >> 8) & 0xff, |
| dev->mmio + NI6527_FILT_INTERVAL_REG(1)); |
| writeb((val >> 16) & 0x0f, |
| dev->mmio + NI6527_FILT_INTERVAL_REG(2)); |
| |
| writeb(NI6527_CLR_INTERVAL, dev->mmio + NI6527_CLR_REG); |
| |
| devpriv->filter_interval = val; |
| } |
| } |
| |
| static void ni6527_set_filter_enable(struct comedi_device *dev, |
| unsigned int val) |
| { |
| writeb(val & 0xff, dev->mmio + NI6527_FILT_ENA_REG(0)); |
| writeb((val >> 8) & 0xff, dev->mmio + NI6527_FILT_ENA_REG(1)); |
| writeb((val >> 16) & 0xff, dev->mmio + NI6527_FILT_ENA_REG(2)); |
| } |
| |
| static int ni6527_di_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct ni6527_private *devpriv = dev->private; |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| unsigned int interval; |
| |
| 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; |
| data[1] = interval * 200; |
| |
| if (interval) { |
| ni6527_set_filter_interval(dev, interval); |
| devpriv->filter_enable |= 1 << chan; |
| } else { |
| devpriv->filter_enable &= ~(1 << chan); |
| } |
| ni6527_set_filter_enable(dev, devpriv->filter_enable); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return insn->n; |
| } |
| |
| static int ni6527_di_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int val; |
| |
| val = readb(dev->mmio + NI6527_DI_REG(0)); |
| val |= (readb(dev->mmio + NI6527_DI_REG(1)) << 8); |
| val |= (readb(dev->mmio + NI6527_DI_REG(2)) << 16); |
| |
| data[1] = val; |
| |
| return insn->n; |
| } |
| |
| static int ni6527_do_insn_bits(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int mask; |
| |
| mask = comedi_dio_update_state(s, data); |
| if (mask) { |
| /* Outputs are inverted */ |
| unsigned int val = s->state ^ 0xffffff; |
| |
| if (mask & 0x0000ff) |
| writeb(val & 0xff, dev->mmio + NI6527_DO_REG(0)); |
| if (mask & 0x00ff00) |
| writeb((val >> 8) & 0xff, |
| dev->mmio + NI6527_DO_REG(1)); |
| if (mask & 0xff0000) |
| writeb((val >> 16) & 0xff, |
| dev->mmio + NI6527_DO_REG(2)); |
| } |
| |
| data[1] = s->state; |
| |
| return insn->n; |
| } |
| |
| static irqreturn_t ni6527_interrupt(int irq, void *d) |
| { |
| struct comedi_device *dev = d; |
| struct comedi_subdevice *s = dev->read_subdev; |
| unsigned int status; |
| |
| status = readb(dev->mmio + NI6527_STATUS_REG); |
| if (!(status & NI6527_STATUS_IRQ)) |
| return IRQ_NONE; |
| |
| if (status & NI6527_STATUS_EDGE) { |
| unsigned short val = 0; |
| |
| comedi_buf_write_samples(s, &val, 1); |
| comedi_handle_events(dev, s); |
| } |
| |
| writeb(NI6527_CLR_IRQS, dev->mmio + NI6527_CLR_REG); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static int ni6527_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 ni6527_intr_cmd(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| writeb(NI6527_CLR_IRQS, dev->mmio + NI6527_CLR_REG); |
| writeb(NI6527_CTRL_ENABLE_IRQS, dev->mmio + NI6527_CTRL_REG); |
| |
| return 0; |
| } |
| |
| static int ni6527_intr_cancel(struct comedi_device *dev, |
| struct comedi_subdevice *s) |
| { |
| writeb(NI6527_CTRL_DISABLE_IRQS, dev->mmio + NI6527_CTRL_REG); |
| |
| return 0; |
| } |
| |
| static int ni6527_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 void ni6527_set_edge_detection(struct comedi_device *dev, |
| unsigned int mask, |
| unsigned int rising, |
| unsigned int falling) |
| { |
| unsigned int i; |
| |
| rising &= mask; |
| falling &= mask; |
| for (i = 0; i < 2; i++) { |
| if (mask & 0xff) { |
| if (~mask & 0xff) { |
| /* preserve rising-edge detection channels */ |
| rising |= readb(dev->mmio + |
| NI6527_RISING_EDGE_REG(i)) & |
| (~mask & 0xff); |
| /* preserve falling-edge detection channels */ |
| falling |= readb(dev->mmio + |
| NI6527_FALLING_EDGE_REG(i)) & |
| (~mask & 0xff); |
| } |
| /* update rising-edge detection channels */ |
| writeb(rising & 0xff, |
| dev->mmio + NI6527_RISING_EDGE_REG(i)); |
| /* update falling-edge detection channels */ |
| writeb(falling & 0xff, |
| dev->mmio + NI6527_FALLING_EDGE_REG(i)); |
| } |
| rising >>= 8; |
| falling >>= 8; |
| mask >>= 8; |
| } |
| } |
| |
| static int ni6527_intr_insn_config(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| unsigned int mask = 0xffffffff; |
| unsigned int rising, falling, shift; |
| |
| switch (data[0]) { |
| case INSN_CONFIG_CHANGE_NOTIFY: |
| /* check_insn_config_length() does not check this instruction */ |
| if (insn->n != 3) |
| return -EINVAL; |
| rising = data[1]; |
| falling = data[2]; |
| ni6527_set_edge_detection(dev, mask, rising, falling); |
| 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: |
| rising = 0; |
| falling = 0; |
| break; |
| case COMEDI_DIGITAL_TRIG_ENABLE_EDGES: |
| /* check shift amount */ |
| shift = data[3]; |
| if (shift >= 32) { |
| mask = 0; |
| rising = 0; |
| falling = 0; |
| } else { |
| mask <<= shift; |
| rising = data[4] << shift; |
| falling = data[5] << shift; |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| ni6527_set_edge_detection(dev, mask, rising, falling); |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return insn->n; |
| } |
| |
| static void ni6527_reset(struct comedi_device *dev) |
| { |
| /* disable deglitch filters on all channels */ |
| ni6527_set_filter_enable(dev, 0); |
| |
| /* disable edge detection */ |
| ni6527_set_edge_detection(dev, 0xffffffff, 0, 0); |
| |
| writeb(NI6527_CLR_IRQS | NI6527_CLR_RESET_FILT, |
| dev->mmio + NI6527_CLR_REG); |
| writeb(NI6527_CTRL_DISABLE_IRQS, dev->mmio + NI6527_CTRL_REG); |
| } |
| |
| static int ni6527_auto_attach(struct comedi_device *dev, |
| unsigned long context) |
| { |
| struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
| const struct ni6527_board *board = NULL; |
| struct ni6527_private *devpriv; |
| struct comedi_subdevice *s; |
| int ret; |
| |
| if (context < ARRAY_SIZE(ni6527_boards)) |
| board = &ni6527_boards[context]; |
| if (!board) |
| return -ENODEV; |
| dev->board_ptr = board; |
| dev->board_name = board->name; |
| |
| devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
| if (!devpriv) |
| return -ENOMEM; |
| |
| ret = comedi_pci_enable(dev); |
| if (ret) |
| return ret; |
| |
| dev->mmio = pci_ioremap_bar(pcidev, 1); |
| if (!dev->mmio) |
| return -ENOMEM; |
| |
| /* make sure this is actually a 6527 device */ |
| if (readb(dev->mmio + NI6527_ID_REG) != 0x27) |
| return -ENODEV; |
| |
| ni6527_reset(dev); |
| |
| ret = request_irq(pcidev->irq, ni6527_interrupt, IRQF_SHARED, |
| dev->board_name, dev); |
| if (ret == 0) |
| dev->irq = pcidev->irq; |
| |
| ret = comedi_alloc_subdevices(dev, 3); |
| if (ret) |
| return ret; |
| |
| /* Digital Input subdevice */ |
| s = &dev->subdevices[0]; |
| s->type = COMEDI_SUBD_DI; |
| s->subdev_flags = SDF_READABLE; |
| s->n_chan = 24; |
| s->maxdata = 1; |
| s->range_table = &range_digital; |
| s->insn_config = ni6527_di_insn_config; |
| s->insn_bits = ni6527_di_insn_bits; |
| |
| /* Digital Output subdevice */ |
| s = &dev->subdevices[1]; |
| s->type = COMEDI_SUBD_DO; |
| s->subdev_flags = SDF_WRITABLE; |
| s->n_chan = 24; |
| s->maxdata = 1; |
| s->range_table = &range_digital; |
| s->insn_bits = ni6527_do_insn_bits; |
| |
| /* Edge detection interrupt subdevice */ |
| s = &dev->subdevices[2]; |
| if (dev->irq) { |
| dev->read_subdev = s; |
| s->type = COMEDI_SUBD_DI; |
| s->subdev_flags = SDF_READABLE | SDF_CMD_READ; |
| s->n_chan = 1; |
| s->maxdata = 1; |
| s->range_table = &range_digital; |
| s->insn_config = ni6527_intr_insn_config; |
| s->insn_bits = ni6527_intr_insn_bits; |
| s->len_chanlist = 1; |
| s->do_cmdtest = ni6527_intr_cmdtest; |
| s->do_cmd = ni6527_intr_cmd; |
| s->cancel = ni6527_intr_cancel; |
| } else { |
| s->type = COMEDI_SUBD_UNUSED; |
| } |
| |
| return 0; |
| } |
| |
| static void ni6527_detach(struct comedi_device *dev) |
| { |
| if (dev->mmio) |
| ni6527_reset(dev); |
| comedi_pci_detach(dev); |
| } |
| |
| static struct comedi_driver ni6527_driver = { |
| .driver_name = "ni_6527", |
| .module = THIS_MODULE, |
| .auto_attach = ni6527_auto_attach, |
| .detach = ni6527_detach, |
| }; |
| |
| static int ni6527_pci_probe(struct pci_dev *dev, |
| const struct pci_device_id *id) |
| { |
| return comedi_pci_auto_config(dev, &ni6527_driver, id->driver_data); |
| } |
| |
| static const struct pci_device_id ni6527_pci_table[] = { |
| { PCI_VDEVICE(NI, 0x2b10), BOARD_PXI6527 }, |
| { PCI_VDEVICE(NI, 0x2b20), BOARD_PCI6527 }, |
| { 0 } |
| }; |
| MODULE_DEVICE_TABLE(pci, ni6527_pci_table); |
| |
| static struct pci_driver ni6527_pci_driver = { |
| .name = "ni_6527", |
| .id_table = ni6527_pci_table, |
| .probe = ni6527_pci_probe, |
| .remove = comedi_pci_auto_unconfig, |
| }; |
| module_comedi_pci_driver(ni6527_driver, ni6527_pci_driver); |
| |
| MODULE_AUTHOR("Comedi https://www.comedi.org"); |
| MODULE_DESCRIPTION("Comedi driver for National Instruments PCI-6527"); |
| MODULE_LICENSE("GPL"); |