| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Comedi driver for NI AT-MIO E series cards |
| * |
| * COMEDI - Linux Control and Measurement Device Interface |
| * Copyright (C) 1997-2001 David A. Schleef <ds@schleef.org> |
| */ |
| |
| /* |
| * Driver: ni_atmio |
| * Description: National Instruments AT-MIO-E series |
| * Author: ds |
| * Devices: [National Instruments] AT-MIO-16E-1 (ni_atmio), |
| * AT-MIO-16E-2, AT-MIO-16E-10, AT-MIO-16DE-10, AT-MIO-64E-3, |
| * AT-MIO-16XE-50, AT-MIO-16XE-10, AT-AI-16XE-10 |
| * Status: works |
| * Updated: Thu May 1 20:03:02 CDT 2003 |
| * |
| * The driver has 2.6 kernel isapnp support, and will automatically probe for |
| * a supported board if the I/O base is left unspecified with comedi_config. |
| * However, many of the isapnp id numbers are unknown. If your board is not |
| * recognized, please send the output of 'cat /proc/isapnp' (you may need to |
| * modprobe the isa-pnp module for /proc/isapnp to exist) so the id numbers |
| * for your board can be added to the driver. |
| * |
| * Otherwise, you can use the isapnptools package to configure your board. |
| * Use isapnp to configure the I/O base and IRQ for the board, and then pass |
| * the same values as parameters in comedi_config. A sample isapnp.conf file |
| * is included in the etc/ directory of Comedilib. |
| * |
| * Comedilib includes a utility to autocalibrate these boards. The boards |
| * seem to boot into a state where the all calibration DACs are at one |
| * extreme of their range, thus the default calibration is terrible. |
| * Calibration at boot is strongly encouraged. |
| * |
| * To use the extended digital I/O on some of the boards, enable the |
| * 8255 driver when configuring the Comedi source tree. |
| * |
| * External triggering is supported for some events. The channel index |
| * (scan_begin_arg, etc.) maps to PFI0 - PFI9. |
| * |
| * Some of the more esoteric triggering possibilities of these boards are |
| * not supported. |
| */ |
| |
| /* |
| * The real guts of the driver is in ni_mio_common.c, which is included |
| * both here and in ni_pcimio.c |
| * |
| * Interrupt support added by Truxton Fulton <trux@truxton.com> |
| * |
| * References for specifications: |
| * 340747b.pdf Register Level Programmer Manual (obsolete) |
| * 340747c.pdf Register Level Programmer Manual (new) |
| * DAQ-STC reference manual |
| * |
| * Other possibly relevant info: |
| * 320517c.pdf User manual (obsolete) |
| * 320517f.pdf User manual (new) |
| * 320889a.pdf delete |
| * 320906c.pdf maximum signal ratings |
| * 321066a.pdf about 16x |
| * 321791a.pdf discontinuation of at-mio-16e-10 rev. c |
| * 321808a.pdf about at-mio-16e-10 rev P |
| * 321837a.pdf discontinuation of at-mio-16de-10 rev d |
| * 321838a.pdf about at-mio-16de-10 rev N |
| * |
| * ISSUES: |
| * - need to deal with external reference for DAC, and other DAC |
| * properties in board properties |
| * - deal with at-mio-16de-10 revision D to N changes, etc. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/interrupt.h> |
| #include <linux/comedi/comedidev.h> |
| #include <linux/isapnp.h> |
| #include <linux/comedi/comedi_8255.h> |
| |
| #include "ni_stc.h" |
| |
| /* AT specific setup */ |
| static const struct ni_board_struct ni_boards[] = { |
| { |
| .name = "at-mio-16e-1", |
| .device_id = 44, |
| .isapnp_id = 0x0000, /* XXX unknown */ |
| .n_adchan = 16, |
| .ai_maxdata = 0x0fff, |
| .ai_fifo_depth = 8192, |
| .gainlkup = ai_gain_16, |
| .ai_speed = 800, |
| .n_aochan = 2, |
| .ao_maxdata = 0x0fff, |
| .ao_fifo_depth = 2048, |
| .ao_range_table = &range_ni_E_ao_ext, |
| .ao_speed = 1000, |
| .caldac = { mb88341 }, |
| }, { |
| .name = "at-mio-16e-2", |
| .device_id = 25, |
| .isapnp_id = 0x1900, |
| .n_adchan = 16, |
| .ai_maxdata = 0x0fff, |
| .ai_fifo_depth = 2048, |
| .gainlkup = ai_gain_16, |
| .ai_speed = 2000, |
| .n_aochan = 2, |
| .ao_maxdata = 0x0fff, |
| .ao_fifo_depth = 2048, |
| .ao_range_table = &range_ni_E_ao_ext, |
| .ao_speed = 1000, |
| .caldac = { mb88341 }, |
| }, { |
| .name = "at-mio-16e-10", |
| .device_id = 36, |
| .isapnp_id = 0x2400, |
| .n_adchan = 16, |
| .ai_maxdata = 0x0fff, |
| .ai_fifo_depth = 512, |
| .gainlkup = ai_gain_16, |
| .ai_speed = 10000, |
| .n_aochan = 2, |
| .ao_maxdata = 0x0fff, |
| .ao_range_table = &range_ni_E_ao_ext, |
| .ao_speed = 10000, |
| .caldac = { ad8804_debug }, |
| }, { |
| .name = "at-mio-16de-10", |
| .device_id = 37, |
| .isapnp_id = 0x2500, |
| .n_adchan = 16, |
| .ai_maxdata = 0x0fff, |
| .ai_fifo_depth = 512, |
| .gainlkup = ai_gain_16, |
| .ai_speed = 10000, |
| .n_aochan = 2, |
| .ao_maxdata = 0x0fff, |
| .ao_range_table = &range_ni_E_ao_ext, |
| .ao_speed = 10000, |
| .caldac = { ad8804_debug }, |
| .has_8255 = 1, |
| }, { |
| .name = "at-mio-64e-3", |
| .device_id = 38, |
| .isapnp_id = 0x2600, |
| .n_adchan = 64, |
| .ai_maxdata = 0x0fff, |
| .ai_fifo_depth = 2048, |
| .gainlkup = ai_gain_16, |
| .ai_speed = 2000, |
| .n_aochan = 2, |
| .ao_maxdata = 0x0fff, |
| .ao_fifo_depth = 2048, |
| .ao_range_table = &range_ni_E_ao_ext, |
| .ao_speed = 1000, |
| .caldac = { ad8804_debug }, |
| }, { |
| .name = "at-mio-16xe-50", |
| .device_id = 39, |
| .isapnp_id = 0x2700, |
| .n_adchan = 16, |
| .ai_maxdata = 0xffff, |
| .ai_fifo_depth = 512, |
| .alwaysdither = 1, |
| .gainlkup = ai_gain_8, |
| .ai_speed = 50000, |
| .n_aochan = 2, |
| .ao_maxdata = 0x0fff, |
| .ao_range_table = &range_bipolar10, |
| .ao_speed = 50000, |
| .caldac = { dac8800, dac8043 }, |
| }, { |
| .name = "at-mio-16xe-10", |
| .device_id = 50, |
| .isapnp_id = 0x0000, /* XXX unknown */ |
| .n_adchan = 16, |
| .ai_maxdata = 0xffff, |
| .ai_fifo_depth = 512, |
| .alwaysdither = 1, |
| .gainlkup = ai_gain_14, |
| .ai_speed = 10000, |
| .n_aochan = 2, |
| .ao_maxdata = 0xffff, |
| .ao_fifo_depth = 2048, |
| .ao_range_table = &range_ni_E_ao_ext, |
| .ao_speed = 1000, |
| .caldac = { dac8800, dac8043, ad8522 }, |
| }, { |
| .name = "at-ai-16xe-10", |
| .device_id = 51, |
| .isapnp_id = 0x0000, /* XXX unknown */ |
| .n_adchan = 16, |
| .ai_maxdata = 0xffff, |
| .ai_fifo_depth = 512, |
| .alwaysdither = 1, /* unknown */ |
| .gainlkup = ai_gain_14, |
| .ai_speed = 10000, |
| .caldac = { dac8800, dac8043, ad8522 }, |
| }, |
| }; |
| |
| static const int ni_irqpin[] = { |
| -1, -1, -1, 0, 1, 2, -1, 3, -1, -1, 4, 5, 6, -1, -1, 7 |
| }; |
| |
| #include "ni_mio_common.c" |
| |
| static const struct pnp_device_id device_ids[] = { |
| {.id = "NIC1900", .driver_data = 0}, |
| {.id = "NIC2400", .driver_data = 0}, |
| {.id = "NIC2500", .driver_data = 0}, |
| {.id = "NIC2600", .driver_data = 0}, |
| {.id = "NIC2700", .driver_data = 0}, |
| {.id = ""} |
| }; |
| |
| MODULE_DEVICE_TABLE(pnp, device_ids); |
| |
| static int ni_isapnp_find_board(struct pnp_dev **dev) |
| { |
| struct pnp_dev *isapnp_dev = NULL; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(ni_boards); i++) { |
| isapnp_dev = |
| pnp_find_dev(NULL, |
| ISAPNP_VENDOR('N', 'I', 'C'), |
| ISAPNP_FUNCTION(ni_boards[i].isapnp_id), |
| NULL); |
| |
| if (!isapnp_dev || !isapnp_dev->card) |
| continue; |
| |
| if (pnp_device_attach(isapnp_dev) < 0) |
| continue; |
| |
| if (pnp_activate_dev(isapnp_dev) < 0) { |
| pnp_device_detach(isapnp_dev); |
| return -EAGAIN; |
| } |
| |
| if (!pnp_port_valid(isapnp_dev, 0) || |
| !pnp_irq_valid(isapnp_dev, 0)) { |
| pnp_device_detach(isapnp_dev); |
| return -ENOMEM; |
| } |
| break; |
| } |
| if (i == ARRAY_SIZE(ni_boards)) |
| return -ENODEV; |
| *dev = isapnp_dev; |
| return 0; |
| } |
| |
| static const struct ni_board_struct *ni_atmio_probe(struct comedi_device *dev) |
| { |
| int device_id = ni_read_eeprom(dev, 511); |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(ni_boards); i++) { |
| const struct ni_board_struct *board = &ni_boards[i]; |
| |
| if (board->device_id == device_id) |
| return board; |
| } |
| if (device_id == 255) |
| dev_err(dev->class_dev, "can't find board\n"); |
| else if (device_id == 0) |
| dev_err(dev->class_dev, |
| "EEPROM read error (?) or device not found\n"); |
| else |
| dev_err(dev->class_dev, |
| "unknown device ID %d -- contact author\n", device_id); |
| |
| return NULL; |
| } |
| |
| static int ni_atmio_attach(struct comedi_device *dev, |
| struct comedi_devconfig *it) |
| { |
| const struct ni_board_struct *board; |
| struct pnp_dev *isapnp_dev; |
| int ret; |
| unsigned long iobase; |
| unsigned int irq; |
| |
| ret = ni_alloc_private(dev); |
| if (ret) |
| return ret; |
| |
| iobase = it->options[0]; |
| irq = it->options[1]; |
| isapnp_dev = NULL; |
| if (iobase == 0) { |
| ret = ni_isapnp_find_board(&isapnp_dev); |
| if (ret < 0) |
| return ret; |
| |
| iobase = pnp_port_start(isapnp_dev, 0); |
| irq = pnp_irq(isapnp_dev, 0); |
| comedi_set_hw_dev(dev, &isapnp_dev->dev); |
| } |
| |
| ret = comedi_request_region(dev, iobase, 0x20); |
| if (ret) |
| return ret; |
| |
| board = ni_atmio_probe(dev); |
| if (!board) |
| return -ENODEV; |
| dev->board_ptr = board; |
| dev->board_name = board->name; |
| |
| /* irq stuff */ |
| |
| if (irq != 0) { |
| if (irq > 15 || ni_irqpin[irq] == -1) |
| return -EINVAL; |
| ret = request_irq(irq, ni_E_interrupt, 0, |
| dev->board_name, dev); |
| if (ret < 0) |
| return -EINVAL; |
| dev->irq = irq; |
| } |
| |
| /* generic E series stuff in ni_mio_common.c */ |
| |
| ret = ni_E_init(dev, ni_irqpin[dev->irq], 0); |
| if (ret < 0) |
| return ret; |
| |
| return 0; |
| } |
| |
| static void ni_atmio_detach(struct comedi_device *dev) |
| { |
| struct pnp_dev *isapnp_dev; |
| |
| mio_common_detach(dev); |
| comedi_legacy_detach(dev); |
| |
| isapnp_dev = dev->hw_dev ? to_pnp_dev(dev->hw_dev) : NULL; |
| if (isapnp_dev) |
| pnp_device_detach(isapnp_dev); |
| } |
| |
| static struct comedi_driver ni_atmio_driver = { |
| .driver_name = "ni_atmio", |
| .module = THIS_MODULE, |
| .attach = ni_atmio_attach, |
| .detach = ni_atmio_detach, |
| }; |
| module_comedi_driver(ni_atmio_driver); |
| |
| MODULE_AUTHOR("Comedi https://www.comedi.org"); |
| MODULE_DESCRIPTION("Comedi low-level driver"); |
| MODULE_LICENSE("GPL"); |
| |