root/drivers/comedi/drivers/addi_apci_3120.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * addi_apci_3120.c
 * Copyright (C) 2004,2005  ADDI-DATA GmbH for the source code of this module.
 *
 *      ADDI-DATA GmbH
 *      Dieselstrasse 3
 *      D-77833 Ottersweier
 *      Tel: +19(0)7223/9493-0
 *      Fax: +49(0)7223/9493-92
 *      http://www.addi-data.com
 *      info@addi-data.com
 */

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/comedi/comedi_pci.h>

#include "amcc_s5933.h"

/*
 * PCI BAR 0 register map (devpriv->amcc)
 * see amcc_s5933.h for register and bit defines
 */
#define APCI3120_FIFO_ADVANCE_ON_BYTE_2         BIT(29)

/*
 * PCI BAR 1 register map (dev->iobase)
 */
#define APCI3120_AI_FIFO_REG                    0x00
#define APCI3120_CTRL_REG                       0x00
#define APCI3120_CTRL_EXT_TRIG                  BIT(15)
#define APCI3120_CTRL_GATE(x)                   BIT(12 + (x))
#define APCI3120_CTRL_PR(x)                     (((x) & 0xf) << 8)
#define APCI3120_CTRL_PA(x)                     (((x) & 0xf) << 0)
#define APCI3120_AI_SOFTTRIG_REG                0x02
#define APCI3120_STATUS_REG                     0x02
#define APCI3120_STATUS_EOC_INT                 BIT(15)
#define APCI3120_STATUS_AMCC_INT                BIT(14)
#define APCI3120_STATUS_EOS_INT                 BIT(13)
#define APCI3120_STATUS_TIMER2_INT              BIT(12)
#define APCI3120_STATUS_INT_MASK                (0xf << 12)
#define APCI3120_STATUS_TO_DI_BITS(x)           (((x) >> 8) & 0xf)
#define APCI3120_STATUS_TO_VERSION(x)           (((x) >> 4) & 0xf)
#define APCI3120_STATUS_FIFO_FULL               BIT(2)
#define APCI3120_STATUS_FIFO_EMPTY              BIT(1)
#define APCI3120_STATUS_DA_READY                BIT(0)
#define APCI3120_TIMER_REG                      0x04
#define APCI3120_CHANLIST_REG                   0x06
#define APCI3120_CHANLIST_INDEX(x)              (((x) & 0xf) << 8)
#define APCI3120_CHANLIST_UNIPOLAR              BIT(7)
#define APCI3120_CHANLIST_GAIN(x)               (((x) & 0x3) << 4)
#define APCI3120_CHANLIST_MUX(x)                (((x) & 0xf) << 0)
#define APCI3120_AO_REG(x)                      (0x08 + (((x) / 4) * 2))
#define APCI3120_AO_MUX(x)                      (((x) & 0x3) << 14)
#define APCI3120_AO_DATA(x)                     ((x) << 0)
#define APCI3120_TIMER_MODE_REG                 0x0c
#define APCI3120_TIMER_MODE(_t, _m)             ((_m) << ((_t) * 2))
#define APCI3120_TIMER_MODE0                    0  /* I8254_MODE0 */
#define APCI3120_TIMER_MODE2                    1  /* I8254_MODE2 */
#define APCI3120_TIMER_MODE4                    2  /* I8254_MODE4 */
#define APCI3120_TIMER_MODE5                    3  /* I8254_MODE5 */
#define APCI3120_TIMER_MODE_MASK(_t)            (3 << ((_t) * 2))
#define APCI3120_CTR0_REG                       0x0d
#define APCI3120_CTR0_DO_BITS(x)                ((x) << 4)
#define APCI3120_CTR0_TIMER_SEL(x)              ((x) << 0)
#define APCI3120_MODE_REG                       0x0e
#define APCI3120_MODE_TIMER2_CLK(x)             (((x) & 0x3) << 6)
#define APCI3120_MODE_TIMER2_CLK_OSC            APCI3120_MODE_TIMER2_CLK(0)
#define APCI3120_MODE_TIMER2_CLK_OUT1           APCI3120_MODE_TIMER2_CLK(1)
#define APCI3120_MODE_TIMER2_CLK_EOC            APCI3120_MODE_TIMER2_CLK(2)
#define APCI3120_MODE_TIMER2_CLK_EOS            APCI3120_MODE_TIMER2_CLK(3)
#define APCI3120_MODE_TIMER2_CLK_MASK           APCI3120_MODE_TIMER2_CLK(3)
#define APCI3120_MODE_TIMER2_AS(x)              (((x) & 0x3) << 4)
#define APCI3120_MODE_TIMER2_AS_TIMER           APCI3120_MODE_TIMER2_AS(0)
#define APCI3120_MODE_TIMER2_AS_COUNTER         APCI3120_MODE_TIMER2_AS(1)
#define APCI3120_MODE_TIMER2_AS_WDOG            APCI3120_MODE_TIMER2_AS(2)
#define APCI3120_MODE_TIMER2_AS_MASK            APCI3120_MODE_TIMER2_AS(3)
#define APCI3120_MODE_SCAN_ENA                  BIT(3)
#define APCI3120_MODE_TIMER2_IRQ_ENA            BIT(2)
#define APCI3120_MODE_EOS_IRQ_ENA               BIT(1)
#define APCI3120_MODE_EOC_IRQ_ENA               BIT(0)

/*
 * PCI BAR 2 register map (devpriv->addon)
 */
#define APCI3120_ADDON_ADDR_REG                 0x00
#define APCI3120_ADDON_DATA_REG                 0x02
#define APCI3120_ADDON_CTRL_REG                 0x04
#define APCI3120_ADDON_CTRL_AMWEN_ENA           BIT(1)
#define APCI3120_ADDON_CTRL_A2P_FIFO_ENA        BIT(0)

/*
 * Board revisions
 */
#define APCI3120_REVA                           0xa
#define APCI3120_REVB                           0xb
#define APCI3120_REVA_OSC_BASE                  70      /* 70ns = 14.29MHz */
#define APCI3120_REVB_OSC_BASE                  50      /* 50ns = 20MHz */

static const struct comedi_lrange apci3120_ai_range = {
        8, {
                BIP_RANGE(10),
                BIP_RANGE(5),
                BIP_RANGE(2),
                BIP_RANGE(1),
                UNI_RANGE(10),
                UNI_RANGE(5),
                UNI_RANGE(2),
                UNI_RANGE(1)
        }
};

enum apci3120_boardid {
        BOARD_APCI3120,
        BOARD_APCI3001,
};

struct apci3120_board {
        const char *name;
        unsigned int ai_is_16bit:1;
        unsigned int has_ao:1;
};

static const struct apci3120_board apci3120_boardtypes[] = {
        [BOARD_APCI3120] = {
                .name           = "apci3120",
                .ai_is_16bit    = 1,
                .has_ao         = 1,
        },
        [BOARD_APCI3001] = {
                .name           = "apci3001",
        },
};

struct apci3120_dmabuf {
        unsigned short *virt;
        dma_addr_t hw;
        unsigned int size;
        unsigned int use_size;
};

struct apci3120_private {
        unsigned long amcc;
        unsigned long addon;
        unsigned int osc_base;
        unsigned int use_dma:1;
        unsigned int use_double_buffer:1;
        unsigned int cur_dmabuf:1;
        struct apci3120_dmabuf dmabuf[2];
        unsigned char do_bits;
        unsigned char timer_mode;
        unsigned char mode;
        unsigned short ctrl;
};

static void apci3120_addon_write(struct comedi_device *dev,
                                 unsigned int val, unsigned int reg)
{
        struct apci3120_private *devpriv = dev->private;

        /* 16-bit interface for AMCC add-on registers */

        outw(reg, devpriv->addon + APCI3120_ADDON_ADDR_REG);
        outw(val & 0xffff, devpriv->addon + APCI3120_ADDON_DATA_REG);

        outw(reg + 2, devpriv->addon + APCI3120_ADDON_ADDR_REG);
        outw((val >> 16) & 0xffff, devpriv->addon + APCI3120_ADDON_DATA_REG);
}

static void apci3120_init_dma(struct comedi_device *dev,
                              struct apci3120_dmabuf *dmabuf)
{
        struct apci3120_private *devpriv = dev->private;

        /* AMCC - enable transfer count and reset A2P FIFO */
        outl(AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO,
             devpriv->amcc + AMCC_OP_REG_AGCSTS);

        /* Add-On - enable transfer count and reset A2P FIFO */
        apci3120_addon_write(dev, AGCSTS_TC_ENABLE | AGCSTS_RESET_A2P_FIFO,
                             AMCC_OP_REG_AGCSTS);

        /* AMCC - enable transfers and reset A2P flags */
        outl(RESET_A2P_FLAGS | EN_A2P_TRANSFERS,
             devpriv->amcc + AMCC_OP_REG_MCSR);

        /* Add-On - DMA start address */
        apci3120_addon_write(dev, dmabuf->hw, AMCC_OP_REG_AMWAR);

        /* Add-On - Number of acquisitions */
        apci3120_addon_write(dev, dmabuf->use_size, AMCC_OP_REG_AMWTC);

        /* AMCC - enable write complete (DMA) and set FIFO advance */
        outl(APCI3120_FIFO_ADVANCE_ON_BYTE_2 | AINT_WRITE_COMPL,
             devpriv->amcc + AMCC_OP_REG_INTCSR);

        /* Add-On - enable DMA */
        outw(APCI3120_ADDON_CTRL_AMWEN_ENA | APCI3120_ADDON_CTRL_A2P_FIFO_ENA,
             devpriv->addon + APCI3120_ADDON_CTRL_REG);
}

static void apci3120_setup_dma(struct comedi_device *dev,
                               struct comedi_subdevice *s)
{
        struct apci3120_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        struct apci3120_dmabuf *dmabuf0 = &devpriv->dmabuf[0];
        struct apci3120_dmabuf *dmabuf1 = &devpriv->dmabuf[1];
        unsigned int dmalen0 = dmabuf0->size;
        unsigned int dmalen1 = dmabuf1->size;
        unsigned int scan_bytes;

        scan_bytes = comedi_samples_to_bytes(s, cmd->scan_end_arg);

        if (cmd->stop_src == TRIG_COUNT) {
                /*
                 * Must we fill full first buffer? And must we fill
                 * full second buffer when first is once filled?
                 */
                if (dmalen0 > (cmd->stop_arg * scan_bytes))
                        dmalen0 = cmd->stop_arg * scan_bytes;
                else if (dmalen1 > (cmd->stop_arg * scan_bytes - dmalen0))
                        dmalen1 = cmd->stop_arg * scan_bytes - dmalen0;
        }

        if (cmd->flags & CMDF_WAKE_EOS) {
                /* don't we want wake up every scan? */
                if (dmalen0 > scan_bytes) {
                        dmalen0 = scan_bytes;
                        if (cmd->scan_end_arg & 1)
                                dmalen0 += 2;
                }
                if (dmalen1 > scan_bytes) {
                        dmalen1 = scan_bytes;
                        if (cmd->scan_end_arg & 1)
                                dmalen1 -= 2;
                        if (dmalen1 < 4)
                                dmalen1 = 4;
                }
        } else {
                /* isn't output buff smaller that our DMA buff? */
                if (dmalen0 > s->async->prealloc_bufsz)
                        dmalen0 = s->async->prealloc_bufsz;
                if (dmalen1 > s->async->prealloc_bufsz)
                        dmalen1 = s->async->prealloc_bufsz;
        }
        dmabuf0->use_size = dmalen0;
        dmabuf1->use_size = dmalen1;

        apci3120_init_dma(dev, dmabuf0);
}

/*
 * There are three timers on the board. They all use the same base
 * clock with a fixed prescaler for each timer. The base clock used
 * depends on the board version and type.
 *
 * APCI-3120 Rev A boards OSC = 14.29MHz base clock (~70ns)
 * APCI-3120 Rev B boards OSC = 20MHz base clock (50ns)
 * APCI-3001 boards OSC = 20MHz base clock (50ns)
 *
 * The prescalers for each timer are:
 * Timer 0 CLK = OSC/10
 * Timer 1 CLK = OSC/1000
 * Timer 2 CLK = OSC/1000
 */
static unsigned int apci3120_ns_to_timer(struct comedi_device *dev,
                                         unsigned int timer,
                                         unsigned int ns,
                                         unsigned int flags)
{
        struct apci3120_private *devpriv = dev->private;
        unsigned int prescale = (timer == 0) ? 10 : 1000;
        unsigned int timer_base = devpriv->osc_base * prescale;
        unsigned int divisor;

        switch (flags & CMDF_ROUND_MASK) {
        case CMDF_ROUND_UP:
                divisor = DIV_ROUND_UP(ns, timer_base);
                break;
        case CMDF_ROUND_DOWN:
                divisor = ns / timer_base;
                break;
        case CMDF_ROUND_NEAREST:
        default:
                divisor = DIV_ROUND_CLOSEST(ns, timer_base);
                break;
        }

        if (timer == 2) {
                /* timer 2 is 24-bits */
                if (divisor > 0x00ffffff)
                        divisor = 0x00ffffff;
        } else {
                /* timers 0 and 1 are 16-bits */
                if (divisor > 0xffff)
                        divisor = 0xffff;
        }
        /* the timers require a minimum divisor of 2 */
        if (divisor < 2)
                divisor = 2;

        return divisor;
}

static void apci3120_clr_timer2_interrupt(struct comedi_device *dev)
{
        /* a dummy read of APCI3120_CTR0_REG clears the timer 2 interrupt */
        inb(dev->iobase + APCI3120_CTR0_REG);
}

static void apci3120_timer_write(struct comedi_device *dev,
                                 unsigned int timer, unsigned int val)
{
        struct apci3120_private *devpriv = dev->private;

        /* write 16-bit value to timer (lower 16-bits of timer 2) */
        outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) |
             APCI3120_CTR0_TIMER_SEL(timer),
             dev->iobase + APCI3120_CTR0_REG);
        outw(val & 0xffff, dev->iobase + APCI3120_TIMER_REG);

        if (timer == 2) {
                /* write upper 16-bits to timer 2 */
                outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) |
                     APCI3120_CTR0_TIMER_SEL(timer + 1),
                     dev->iobase + APCI3120_CTR0_REG);
                outw((val >> 16) & 0xffff, dev->iobase + APCI3120_TIMER_REG);
        }
}

static unsigned int apci3120_timer_read(struct comedi_device *dev,
                                        unsigned int timer)
{
        struct apci3120_private *devpriv = dev->private;
        unsigned int val;

        /* read 16-bit value from timer (lower 16-bits of timer 2) */
        outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) |
             APCI3120_CTR0_TIMER_SEL(timer),
             dev->iobase + APCI3120_CTR0_REG);
        val = inw(dev->iobase + APCI3120_TIMER_REG);

        if (timer == 2) {
                /* read upper 16-bits from timer 2 */
                outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits) |
                     APCI3120_CTR0_TIMER_SEL(timer + 1),
                     dev->iobase + APCI3120_CTR0_REG);
                val |= (inw(dev->iobase + APCI3120_TIMER_REG) << 16);
        }

        return val;
}

static void apci3120_timer_set_mode(struct comedi_device *dev,
                                    unsigned int timer, unsigned int mode)
{
        struct apci3120_private *devpriv = dev->private;

        devpriv->timer_mode &= ~APCI3120_TIMER_MODE_MASK(timer);
        devpriv->timer_mode |= APCI3120_TIMER_MODE(timer, mode);
        outb(devpriv->timer_mode, dev->iobase + APCI3120_TIMER_MODE_REG);
}

static void apci3120_timer_enable(struct comedi_device *dev,
                                  unsigned int timer, bool enable)
{
        struct apci3120_private *devpriv = dev->private;

        if (enable)
                devpriv->ctrl |= APCI3120_CTRL_GATE(timer);
        else
                devpriv->ctrl &= ~APCI3120_CTRL_GATE(timer);
        outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG);
}

static void apci3120_exttrig_enable(struct comedi_device *dev, bool enable)
{
        struct apci3120_private *devpriv = dev->private;

        if (enable)
                devpriv->ctrl |= APCI3120_CTRL_EXT_TRIG;
        else
                devpriv->ctrl &= ~APCI3120_CTRL_EXT_TRIG;
        outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG);
}

static void apci3120_set_chanlist(struct comedi_device *dev,
                                  struct comedi_subdevice *s,
                                  int n_chan, unsigned int *chanlist)
{
        struct apci3120_private *devpriv = dev->private;
        int i;

        /* set chanlist for scan */
        for (i = 0; i < n_chan; i++) {
                unsigned int chan = CR_CHAN(chanlist[i]);
                unsigned int range = CR_RANGE(chanlist[i]);
                unsigned int val;

                val = APCI3120_CHANLIST_MUX(chan) |
                      APCI3120_CHANLIST_GAIN(range) |
                      APCI3120_CHANLIST_INDEX(i);

                if (comedi_range_is_unipolar(s, range))
                        val |= APCI3120_CHANLIST_UNIPOLAR;

                outw(val, dev->iobase + APCI3120_CHANLIST_REG);
        }

        /* a dummy read of APCI3120_TIMER_MODE_REG resets the ai FIFO */
        inw(dev->iobase + APCI3120_TIMER_MODE_REG);

        /* set scan length (PR) and scan start (PA) */
        devpriv->ctrl = APCI3120_CTRL_PR(n_chan - 1) | APCI3120_CTRL_PA(0);
        outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG);

        /* enable chanlist scanning if necessary */
        if (n_chan > 1)
                devpriv->mode |= APCI3120_MODE_SCAN_ENA;
}

static void apci3120_interrupt_dma(struct comedi_device *dev,
                                   struct comedi_subdevice *s)
{
        struct apci3120_private *devpriv = dev->private;
        struct comedi_async *async = s->async;
        struct comedi_cmd *cmd = &async->cmd;
        struct apci3120_dmabuf *dmabuf;
        unsigned int nbytes;
        unsigned int nsamples;

        dmabuf = &devpriv->dmabuf[devpriv->cur_dmabuf];

        nbytes = dmabuf->use_size - inl(devpriv->amcc + AMCC_OP_REG_MWTC);

        if (nbytes < dmabuf->use_size)
                dev_err(dev->class_dev, "Interrupted DMA transfer!\n");
        if (nbytes & 1) {
                dev_err(dev->class_dev, "Odd count of bytes in DMA ring!\n");
                async->events |= COMEDI_CB_ERROR;
                return;
        }

        nsamples = comedi_bytes_to_samples(s, nbytes);
        if (nsamples) {
                comedi_buf_write_samples(s, dmabuf->virt, nsamples);

                if (!(cmd->flags & CMDF_WAKE_EOS))
                        async->events |= COMEDI_CB_EOS;
        }

        if ((async->events & COMEDI_CB_CANCEL_MASK) ||
            (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg))
                return;

        if (devpriv->use_double_buffer) {
                /* switch DMA buffers for next interrupt */
                devpriv->cur_dmabuf = !devpriv->cur_dmabuf;
                dmabuf = &devpriv->dmabuf[devpriv->cur_dmabuf];
                apci3120_init_dma(dev, dmabuf);
        } else {
                /* restart DMA if not using double buffering */
                apci3120_init_dma(dev, dmabuf);
        }
}

static irqreturn_t apci3120_interrupt(int irq, void *d)
{
        struct comedi_device *dev = d;
        struct apci3120_private *devpriv = dev->private;
        struct comedi_subdevice *s = dev->read_subdev;
        struct comedi_async *async = s->async;
        struct comedi_cmd *cmd = &async->cmd;
        unsigned int status;
        unsigned int int_amcc;

        status = inw(dev->iobase + APCI3120_STATUS_REG);
        int_amcc = inl(devpriv->amcc + AMCC_OP_REG_INTCSR);

        if (!(status & APCI3120_STATUS_INT_MASK) &&
            !(int_amcc & ANY_S593X_INT)) {
                dev_err(dev->class_dev, "IRQ from unknown source\n");
                return IRQ_NONE;
        }

        outl(int_amcc | AINT_INT_MASK, devpriv->amcc + AMCC_OP_REG_INTCSR);

        if (devpriv->ctrl & APCI3120_CTRL_EXT_TRIG)
                apci3120_exttrig_enable(dev, false);

        if (int_amcc & MASTER_ABORT_INT)
                dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n");
        if (int_amcc & TARGET_ABORT_INT)
                dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n");

        if ((status & APCI3120_STATUS_EOS_INT) &&
            (devpriv->mode & APCI3120_MODE_EOS_IRQ_ENA)) {
                unsigned short val;
                int i;

                for (i = 0; i < cmd->chanlist_len; i++) {
                        val = inw(dev->iobase + APCI3120_AI_FIFO_REG);
                        comedi_buf_write_samples(s, &val, 1);
                }

                devpriv->mode |= APCI3120_MODE_EOS_IRQ_ENA;
                outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);
        }

        if (status & APCI3120_STATUS_TIMER2_INT) {
                /*
                 * for safety...
                 * timer2 interrupts are not enabled in the driver
                 */
                apci3120_clr_timer2_interrupt(dev);
        }

        if (status & APCI3120_STATUS_AMCC_INT) {
                /* AMCC- Clear write complete interrupt (DMA) */
                outl(AINT_WT_COMPLETE, devpriv->amcc + AMCC_OP_REG_INTCSR);

                /* do some data transfer */
                apci3120_interrupt_dma(dev, s);
        }

        if (cmd->stop_src == TRIG_COUNT && async->scans_done >= cmd->stop_arg)
                async->events |= COMEDI_CB_EOA;

        comedi_handle_events(dev, s);

        return IRQ_HANDLED;
}

static int apci3120_ai_cmd(struct comedi_device *dev,
                           struct comedi_subdevice *s)
{
        struct apci3120_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        unsigned int divisor;

        /* set default mode bits */
        devpriv->mode = APCI3120_MODE_TIMER2_CLK_OSC |
                        APCI3120_MODE_TIMER2_AS_TIMER;

        /* AMCC- Clear write complete interrupt (DMA) */
        outl(AINT_WT_COMPLETE, devpriv->amcc + AMCC_OP_REG_INTCSR);

        devpriv->cur_dmabuf = 0;

        /* load chanlist for command scan */
        apci3120_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist);

        if (cmd->start_src == TRIG_EXT)
                apci3120_exttrig_enable(dev, true);

        if (cmd->scan_begin_src == TRIG_TIMER) {
                /*
                 * Timer 1 is used in MODE2 (rate generator) to set the
                 * start time for each scan.
                 */
                divisor = apci3120_ns_to_timer(dev, 1, cmd->scan_begin_arg,
                                               cmd->flags);
                apci3120_timer_set_mode(dev, 1, APCI3120_TIMER_MODE2);
                apci3120_timer_write(dev, 1, divisor);
        }

        /*
         * Timer 0 is used in MODE2 (rate generator) to set the conversion
         * time for each acquisition.
         */
        divisor = apci3120_ns_to_timer(dev, 0, cmd->convert_arg, cmd->flags);
        apci3120_timer_set_mode(dev, 0, APCI3120_TIMER_MODE2);
        apci3120_timer_write(dev, 0, divisor);

        if (devpriv->use_dma)
                apci3120_setup_dma(dev, s);
        else
                devpriv->mode |= APCI3120_MODE_EOS_IRQ_ENA;

        /* set mode to enable acquisition */
        outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);

        if (cmd->scan_begin_src == TRIG_TIMER)
                apci3120_timer_enable(dev, 1, true);
        apci3120_timer_enable(dev, 0, true);

        return 0;
}

static int apci3120_ai_cmdtest(struct comedi_device *dev,
                               struct comedi_subdevice *s,
                               struct comedi_cmd *cmd)
{
        unsigned int arg;
        int err = 0;

        /* Step 1 : check if triggers are trivially valid */

        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_EXT);
        err |= comedi_check_trigger_src(&cmd->scan_begin_src,
                                        TRIG_TIMER | TRIG_FOLLOW);
        err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_TIMER);
        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);

        if (err)
                return 1;

        /* Step 2a : make sure trigger sources are unique */

        err |= comedi_check_trigger_is_unique(cmd->start_src);
        err |= comedi_check_trigger_is_unique(cmd->scan_begin_src);
        err |= comedi_check_trigger_is_unique(cmd->stop_src);

        /* Step 2b : and mutually compatible */

        if (err)
                return 2;

        /* Step 3: check if arguments are trivially valid */

        err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);

        if (cmd->scan_begin_src == TRIG_TIMER) {        /* Test Delay timing */
                err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
                                                    100000);
        }

        /* minimum conversion time per sample is 10us */
        err |= comedi_check_trigger_arg_min(&cmd->convert_arg, 10000);

        err |= comedi_check_trigger_arg_min(&cmd->chanlist_len, 1);
        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
                                           cmd->chanlist_len);

        if (cmd->stop_src == TRIG_COUNT)
                err |= comedi_check_trigger_arg_min(&cmd->stop_arg, 1);
        else    /*  TRIG_NONE */
                err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);

        if (err)
                return 3;

        /* Step 4: fix up any arguments */

        if (cmd->scan_begin_src == TRIG_TIMER) {
                /* scan begin must be larger than the scan time */
                arg = cmd->convert_arg * cmd->scan_end_arg;
                err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg, arg);
        }

        if (err)
                return 4;

        /* Step 5: check channel list if it exists */

        return 0;
}

static int apci3120_cancel(struct comedi_device *dev,
                           struct comedi_subdevice *s)
{
        struct apci3120_private *devpriv = dev->private;

        /* Add-On - disable DMA */
        outw(0, devpriv->addon + 4);

        /* Add-On - disable bus master */
        apci3120_addon_write(dev, 0, AMCC_OP_REG_AGCSTS);

        /* AMCC - disable bus master */
        outl(0, devpriv->amcc + AMCC_OP_REG_MCSR);

        /* disable all counters, ext trigger, and reset scan */
        devpriv->ctrl = 0;
        outw(devpriv->ctrl, dev->iobase + APCI3120_CTRL_REG);

        /* DISABLE_ALL_INTERRUPT */
        devpriv->mode = 0;
        outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);

        inw(dev->iobase + APCI3120_STATUS_REG);
        devpriv->cur_dmabuf = 0;

        return 0;
}

static int apci3120_ai_eoc(struct comedi_device *dev,
                           struct comedi_subdevice *s,
                           struct comedi_insn *insn,
                           unsigned long context)
{
        unsigned int status;

        status = inw(dev->iobase + APCI3120_STATUS_REG);
        if ((status & APCI3120_STATUS_EOC_INT) == 0)
                return 0;
        return -EBUSY;
}

static int apci3120_ai_insn_read(struct comedi_device *dev,
                                 struct comedi_subdevice *s,
                                 struct comedi_insn *insn,
                                 unsigned int *data)
{
        struct apci3120_private *devpriv = dev->private;
        unsigned int divisor;
        int ret;
        int i;

        /* set mode for A/D conversions by software trigger with timer 0 */
        devpriv->mode = APCI3120_MODE_TIMER2_CLK_OSC |
                        APCI3120_MODE_TIMER2_AS_TIMER;
        outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);

        /* load chanlist for single channel scan */
        apci3120_set_chanlist(dev, s, 1, &insn->chanspec);

        /*
         * Timer 0 is used in MODE4 (software triggered strobe) to set the
         * conversion time for each acquisition. Each conversion is triggered
         * when the divisor is written to the timer, The conversion is done
         * when the EOC bit in the status register is '0'.
         */
        apci3120_timer_set_mode(dev, 0, APCI3120_TIMER_MODE4);
        apci3120_timer_enable(dev, 0, true);

        /* fixed conversion time of 10 us */
        divisor = apci3120_ns_to_timer(dev, 0, 10000, CMDF_ROUND_NEAREST);

        for (i = 0; i < insn->n; i++) {
                /* trigger conversion */
                apci3120_timer_write(dev, 0, divisor);

                ret = comedi_timeout(dev, s, insn, apci3120_ai_eoc, 0);
                if (ret)
                        return ret;

                data[i] = inw(dev->iobase + APCI3120_AI_FIFO_REG);
        }

        return insn->n;
}

static int apci3120_ao_ready(struct comedi_device *dev,
                             struct comedi_subdevice *s,
                             struct comedi_insn *insn,
                             unsigned long context)
{
        unsigned int status;

        status = inw(dev->iobase + APCI3120_STATUS_REG);
        if (status & APCI3120_STATUS_DA_READY)
                return 0;
        return -EBUSY;
}

static int apci3120_ao_insn_write(struct comedi_device *dev,
                                  struct comedi_subdevice *s,
                                  struct comedi_insn *insn,
                                  unsigned int *data)
{
        unsigned int chan = CR_CHAN(insn->chanspec);
        int i;

        for (i = 0; i < insn->n; i++) {
                unsigned int val = data[i];
                int ret;

                ret = comedi_timeout(dev, s, insn, apci3120_ao_ready, 0);
                if (ret)
                        return ret;

                outw(APCI3120_AO_MUX(chan) | APCI3120_AO_DATA(val),
                     dev->iobase + APCI3120_AO_REG(chan));

                s->readback[chan] = val;
        }

        return insn->n;
}

static int apci3120_di_insn_bits(struct comedi_device *dev,
                                 struct comedi_subdevice *s,
                                 struct comedi_insn *insn,
                                 unsigned int *data)
{
        unsigned int status;

        status = inw(dev->iobase + APCI3120_STATUS_REG);
        data[1] = APCI3120_STATUS_TO_DI_BITS(status);

        return insn->n;
}

static int apci3120_do_insn_bits(struct comedi_device *dev,
                                 struct comedi_subdevice *s,
                                 struct comedi_insn *insn,
                                 unsigned int *data)
{
        struct apci3120_private *devpriv = dev->private;

        if (comedi_dio_update_state(s, data)) {
                devpriv->do_bits = s->state;
                outb(APCI3120_CTR0_DO_BITS(devpriv->do_bits),
                     dev->iobase + APCI3120_CTR0_REG);
        }

        data[1] = s->state;

        return insn->n;
}

static int apci3120_timer_insn_config(struct comedi_device *dev,
                                      struct comedi_subdevice *s,
                                      struct comedi_insn *insn,
                                      unsigned int *data)
{
        struct apci3120_private *devpriv = dev->private;
        unsigned int divisor;
        unsigned int status;
        unsigned int mode;
        unsigned int timer_mode;

        switch (data[0]) {
        case INSN_CONFIG_ARM:
                apci3120_clr_timer2_interrupt(dev);
                divisor = apci3120_ns_to_timer(dev, 2, data[1],
                                               CMDF_ROUND_DOWN);
                apci3120_timer_write(dev, 2, divisor);
                apci3120_timer_enable(dev, 2, true);
                break;

        case INSN_CONFIG_DISARM:
                apci3120_timer_enable(dev, 2, false);
                apci3120_clr_timer2_interrupt(dev);
                break;

        case INSN_CONFIG_GET_COUNTER_STATUS:
                data[1] = 0;
                data[2] = COMEDI_COUNTER_ARMED | COMEDI_COUNTER_COUNTING |
                          COMEDI_COUNTER_TERMINAL_COUNT;

                if (devpriv->ctrl & APCI3120_CTRL_GATE(2)) {
                        data[1] |= COMEDI_COUNTER_ARMED;
                        data[1] |= COMEDI_COUNTER_COUNTING;
                }
                status = inw(dev->iobase + APCI3120_STATUS_REG);
                if (status & APCI3120_STATUS_TIMER2_INT) {
                        data[1] &= ~COMEDI_COUNTER_COUNTING;
                        data[1] |= COMEDI_COUNTER_TERMINAL_COUNT;
                }
                break;

        case INSN_CONFIG_SET_COUNTER_MODE:
                switch (data[1]) {
                case I8254_MODE0:
                        mode = APCI3120_MODE_TIMER2_AS_COUNTER;
                        timer_mode = APCI3120_TIMER_MODE0;
                        break;
                case I8254_MODE2:
                        mode = APCI3120_MODE_TIMER2_AS_TIMER;
                        timer_mode = APCI3120_TIMER_MODE2;
                        break;
                case I8254_MODE4:
                        mode = APCI3120_MODE_TIMER2_AS_TIMER;
                        timer_mode = APCI3120_TIMER_MODE4;
                        break;
                case I8254_MODE5:
                        mode = APCI3120_MODE_TIMER2_AS_WDOG;
                        timer_mode = APCI3120_TIMER_MODE5;
                        break;
                default:
                        return -EINVAL;
                }
                apci3120_timer_enable(dev, 2, false);
                apci3120_clr_timer2_interrupt(dev);
                apci3120_timer_set_mode(dev, 2, timer_mode);
                devpriv->mode &= ~APCI3120_MODE_TIMER2_AS_MASK;
                devpriv->mode |= mode;
                outb(devpriv->mode, dev->iobase + APCI3120_MODE_REG);
                break;

        default:
                return -EINVAL;
        }

        return insn->n;
}

static int apci3120_timer_insn_read(struct comedi_device *dev,
                                    struct comedi_subdevice *s,
                                    struct comedi_insn *insn,
                                    unsigned int *data)
{
        int i;

        for (i = 0; i < insn->n; i++)
                data[i] = apci3120_timer_read(dev, 2);

        return insn->n;
}

static void apci3120_dma_alloc(struct comedi_device *dev)
{
        struct apci3120_private *devpriv = dev->private;
        struct apci3120_dmabuf *dmabuf;
        int order;
        int i;

        for (i = 0; i < 2; i++) {
                dmabuf = &devpriv->dmabuf[i];
                for (order = 2; order >= 0; order--) {
                        dmabuf->virt = dma_alloc_coherent(dev->hw_dev,
                                                          PAGE_SIZE << order,
                                                          &dmabuf->hw,
                                                          GFP_KERNEL);
                        if (dmabuf->virt)
                                break;
                }
                if (!dmabuf->virt)
                        break;
                dmabuf->size = PAGE_SIZE << order;

                if (i == 0)
                        devpriv->use_dma = 1;
                if (i == 1)
                        devpriv->use_double_buffer = 1;
        }
}

static void apci3120_dma_free(struct comedi_device *dev)
{
        struct apci3120_private *devpriv = dev->private;
        struct apci3120_dmabuf *dmabuf;
        int i;

        if (!devpriv)
                return;

        for (i = 0; i < 2; i++) {
                dmabuf = &devpriv->dmabuf[i];
                if (dmabuf->virt) {
                        dma_free_coherent(dev->hw_dev, dmabuf->size,
                                          dmabuf->virt, dmabuf->hw);
                }
        }
}

static void apci3120_reset(struct comedi_device *dev)
{
        /* disable all interrupt sources */
        outb(0, dev->iobase + APCI3120_MODE_REG);

        /* disable all counters, ext trigger, and reset scan */
        outw(0, dev->iobase + APCI3120_CTRL_REG);

        /* clear interrupt status */
        inw(dev->iobase + APCI3120_STATUS_REG);
}

static int apci3120_auto_attach(struct comedi_device *dev,
                                unsigned long context)
{
        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
        const struct apci3120_board *board = NULL;
        struct apci3120_private *devpriv;
        struct comedi_subdevice *s;
        unsigned int status;
        int ret;

        if (context < ARRAY_SIZE(apci3120_boardtypes))
                board = &apci3120_boardtypes[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;
        pci_set_master(pcidev);

        dev->iobase = pci_resource_start(pcidev, 1);
        devpriv->amcc = pci_resource_start(pcidev, 0);
        devpriv->addon = pci_resource_start(pcidev, 2);

        apci3120_reset(dev);

        if (pcidev->irq > 0) {
                ret = request_irq(pcidev->irq, apci3120_interrupt, IRQF_SHARED,
                                  dev->board_name, dev);
                if (ret == 0) {
                        dev->irq = pcidev->irq;

                        apci3120_dma_alloc(dev);
                }
        }

        status = inw(dev->iobase + APCI3120_STATUS_REG);
        if (APCI3120_STATUS_TO_VERSION(status) == APCI3120_REVB ||
            context == BOARD_APCI3001)
                devpriv->osc_base = APCI3120_REVB_OSC_BASE;
        else
                devpriv->osc_base = APCI3120_REVA_OSC_BASE;

        ret = comedi_alloc_subdevices(dev, 5);
        if (ret)
                return ret;

        /* Analog Input subdevice */
        s = &dev->subdevices[0];
        s->type         = COMEDI_SUBD_AI;
        s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
        s->n_chan       = 16;
        s->maxdata      = board->ai_is_16bit ? 0xffff : 0x0fff;
        s->range_table  = &apci3120_ai_range;
        s->insn_read    = apci3120_ai_insn_read;
        if (dev->irq) {
                dev->read_subdev = s;
                s->subdev_flags |= SDF_CMD_READ;
                s->len_chanlist = s->n_chan;
                s->do_cmdtest   = apci3120_ai_cmdtest;
                s->do_cmd       = apci3120_ai_cmd;
                s->cancel       = apci3120_cancel;
        }

        /* Analog Output subdevice */
        s = &dev->subdevices[1];
        if (board->has_ao) {
                s->type         = COMEDI_SUBD_AO;
                s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
                s->n_chan       = 8;
                s->maxdata      = 0x3fff;
                s->range_table  = &range_bipolar10;
                s->insn_write   = apci3120_ao_insn_write;

                ret = comedi_alloc_subdev_readback(s);
                if (ret)
                        return ret;
        } else {
                s->type         = COMEDI_SUBD_UNUSED;
        }

        /* Digital Input subdevice */
        s = &dev->subdevices[2];
        s->type         = COMEDI_SUBD_DI;
        s->subdev_flags = SDF_READABLE;
        s->n_chan       = 4;
        s->maxdata      = 1;
        s->range_table  = &range_digital;
        s->insn_bits    = apci3120_di_insn_bits;

        /* Digital Output subdevice */
        s = &dev->subdevices[3];
        s->type         = COMEDI_SUBD_DO;
        s->subdev_flags = SDF_WRITABLE;
        s->n_chan       = 4;
        s->maxdata      = 1;
        s->range_table  = &range_digital;
        s->insn_bits    = apci3120_do_insn_bits;

        /* Timer subdevice */
        s = &dev->subdevices[4];
        s->type         = COMEDI_SUBD_TIMER;
        s->subdev_flags = SDF_READABLE;
        s->n_chan       = 1;
        s->maxdata      = 0x00ffffff;
        s->insn_config  = apci3120_timer_insn_config;
        s->insn_read    = apci3120_timer_insn_read;

        return 0;
}

static void apci3120_detach(struct comedi_device *dev)
{
        comedi_pci_detach(dev);
        apci3120_dma_free(dev);
}

static struct comedi_driver apci3120_driver = {
        .driver_name    = "addi_apci_3120",
        .module         = THIS_MODULE,
        .auto_attach    = apci3120_auto_attach,
        .detach         = apci3120_detach,
};

static int apci3120_pci_probe(struct pci_dev *dev,
                              const struct pci_device_id *id)
{
        return comedi_pci_auto_config(dev, &apci3120_driver, id->driver_data);
}

static const struct pci_device_id apci3120_pci_table[] = {
        { PCI_VDEVICE(AMCC, 0x818d), BOARD_APCI3120 },
        { PCI_VDEVICE(AMCC, 0x828d), BOARD_APCI3001 },
        { 0 }
};
MODULE_DEVICE_TABLE(pci, apci3120_pci_table);

static struct pci_driver apci3120_pci_driver = {
        .name           = "addi_apci_3120",
        .id_table       = apci3120_pci_table,
        .probe          = apci3120_pci_probe,
        .remove         = comedi_pci_auto_unconfig,
};
module_comedi_pci_driver(apci3120_driver, apci3120_pci_driver);

MODULE_AUTHOR("Comedi https://www.comedi.org");
MODULE_DESCRIPTION("ADDI-DATA APCI-3120, Analog input board");
MODULE_LICENSE("GPL");