root/drivers/comedi/drivers/ni_atmio.c
// 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"

static const struct comedi_lrange range_ni_E_ao_ext = {
        4, {
                BIP_RANGE(10),
                UNI_RANGE(10),
                RANGE_ext(-1, 1),
                RANGE_ext(0, 1)
        }
};

/* 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 __maybe_unused 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");