root/drivers/comedi/drivers/adl_pci9118.c
// SPDX-License-Identifier: GPL-2.0
/*
 *  comedi/drivers/adl_pci9118.c
 *
 *  hardware driver for ADLink cards:
 *   card:   PCI-9118DG, PCI-9118HG, PCI-9118HR
 *   driver: pci9118dg,  pci9118hg,  pci9118hr
 *
 * Author: Michal Dobes <dobes@tesnet.cz>
 *
 */

/*
 * Driver: adl_pci9118
 * Description: Adlink PCI-9118DG, PCI-9118HG, PCI-9118HR
 * Author: Michal Dobes <dobes@tesnet.cz>
 * Devices: [ADLink] PCI-9118DG (pci9118dg), PCI-9118HG (pci9118hg),
 * PCI-9118HR (pci9118hr)
 * Status: works
 *
 * This driver supports AI, AO, DI and DO subdevices.
 * AI subdevice supports cmd and insn interface,
 * other subdevices support only insn interface.
 * For AI:
 * - If cmd->scan_begin_src=TRIG_EXT then trigger input is TGIN (pin 46).
 * - If cmd->convert_src=TRIG_EXT then trigger input is EXTTRG (pin 44).
 * - If cmd->start_src/stop_src=TRIG_EXT then trigger input is TGIN (pin 46).
 * - It is not necessary to have cmd.scan_end_arg=cmd.chanlist_len but
 * cmd.scan_end_arg modulo cmd.chanlist_len must by 0.
 * - If return value of cmdtest is 5 then you've bad channel list
 * (it isn't possible mixture S.E. and DIFF inputs or bipolar and unipolar
 * ranges).
 *
 * There are some hardware limitations:
 * a) You can't use mixture of unipolar/bipolar ranges or differential/single
 *  ended inputs.
 * b) DMA transfers must have the length aligned to two samples (32 bit),
 *  so there is some problems if cmd->chanlist_len is odd. This driver tries
 *  bypass this with adding one sample to the end of the every scan and discard
 *  it on output but this can't be used if cmd->scan_begin_src=TRIG_FOLLOW
 *  and is used flag CMDF_WAKE_EOS, then driver switch to interrupt driven mode
 *  with interrupt after every sample.
 * c) If isn't used DMA then you can use only mode where
 *  cmd->scan_begin_src=TRIG_FOLLOW.
 *
 * Configuration options:
 * [0] - PCI bus of device (optional)
 * [1] - PCI slot of device (optional)
 *       If bus/slot is not specified, then first available PCI
 *       card will be used.
 * [2] - 0= standard 8 DIFF/16 SE channels configuration
 *       n = external multiplexer connected, 1 <= n <= 256
 * [3] - ignored
 * [4] - sample&hold signal - card can generate signal for external S&H board
 *       0 = use SSHO(pin 45) signal is generated in onboard hardware S&H logic
 *       0 != use ADCHN7(pin 23) signal is generated from driver, number say how
 *              long delay is requested in ns and sign polarity of the hold
 *              (in this case external multiplexor can serve only 128 channels)
 * [5] - ignored
 */

/*
 * FIXME
 *
 * All the supported boards have the same PCI vendor and device IDs, so
 * auto-attachment of PCI devices will always find the first board type.
 *
 * Perhaps the boards have different subdevice IDs that we could use to
 * distinguish them?
 *
 * Need some device attributes so the board type can be corrected after
 * attachment if necessary, and possibly to set other options supported by
 * manual attachment.
 */

#include <linux/module.h>
#include <linux/delay.h>
#include <linux/gfp.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/comedi/comedi_pci.h>
#include <linux/comedi/comedi_8254.h>

#include "amcc_s5933.h"

/*
 * PCI BAR2 Register map (dev->iobase)
 */
#define PCI9118_TIMER_BASE              0x00
#define PCI9118_AI_FIFO_REG             0x10
#define PCI9118_AO_REG(x)               (0x10 + ((x) * 4))
#define PCI9118_AI_STATUS_REG           0x18
#define PCI9118_AI_STATUS_NFULL         BIT(8)  /* 0=FIFO full (fatal) */
#define PCI9118_AI_STATUS_NHFULL        BIT(7)  /* 0=FIFO half full */
#define PCI9118_AI_STATUS_NEPTY         BIT(6)  /* 0=FIFO empty */
#define PCI9118_AI_STATUS_ACMP          BIT(5)  /* 1=about trigger complete */
#define PCI9118_AI_STATUS_DTH           BIT(4)  /* 1=ext. digital trigger */
#define PCI9118_AI_STATUS_BOVER         BIT(3)  /* 1=burst overrun (fatal) */
#define PCI9118_AI_STATUS_ADOS          BIT(2)  /* 1=A/D over speed (warn) */
#define PCI9118_AI_STATUS_ADOR          BIT(1)  /* 1=A/D overrun (fatal) */
#define PCI9118_AI_STATUS_ADRDY         BIT(0)  /* 1=A/D ready */
#define PCI9118_AI_CTRL_REG             0x18
#define PCI9118_AI_CTRL_UNIP            BIT(7)  /* 1=unipolar */
#define PCI9118_AI_CTRL_DIFF            BIT(6)  /* 1=differential inputs */
#define PCI9118_AI_CTRL_SOFTG           BIT(5)  /* 1=8254 software gate */
#define PCI9118_AI_CTRL_EXTG            BIT(4)  /* 1=8254 TGIN(pin 46) gate */
#define PCI9118_AI_CTRL_EXTM            BIT(3)  /* 1=ext. trigger (pin 44) */
#define PCI9118_AI_CTRL_TMRTR           BIT(2)  /* 1=8254 is trigger source */
#define PCI9118_AI_CTRL_INT             BIT(1)  /* 1=enable interrupt */
#define PCI9118_AI_CTRL_DMA             BIT(0)  /* 1=enable DMA */
#define PCI9118_DIO_REG                 0x1c
#define PCI9118_SOFTTRG_REG             0x20
#define PCI9118_AI_CHANLIST_REG         0x24
#define PCI9118_AI_CHANLIST_RANGE(x)    (((x) & 0x3) << 8)
#define PCI9118_AI_CHANLIST_CHAN(x)     ((x) << 0)
#define PCI9118_AI_BURST_NUM_REG        0x28
#define PCI9118_AI_AUTOSCAN_MODE_REG    0x2c
#define PCI9118_AI_CFG_REG              0x30
#define PCI9118_AI_CFG_PDTRG            BIT(7)  /* 1=positive trigger */
#define PCI9118_AI_CFG_PETRG            BIT(6)  /* 1=positive ext. trigger */
#define PCI9118_AI_CFG_BSSH             BIT(5)  /* 1=with sample & hold */
#define PCI9118_AI_CFG_BM               BIT(4)  /* 1=burst mode */
#define PCI9118_AI_CFG_BS               BIT(3)  /* 1=burst mode start */
#define PCI9118_AI_CFG_PM               BIT(2)  /* 1=post trigger */
#define PCI9118_AI_CFG_AM               BIT(1)  /* 1=about trigger */
#define PCI9118_AI_CFG_START            BIT(0)  /* 1=trigger start */
#define PCI9118_FIFO_RESET_REG          0x34
#define PCI9118_INT_CTRL_REG            0x38
#define PCI9118_INT_CTRL_TIMER          BIT(3)  /* timer interrupt */
#define PCI9118_INT_CTRL_ABOUT          BIT(2)  /* about trigger complete */
#define PCI9118_INT_CTRL_HFULL          BIT(1)  /* A/D FIFO half full */
#define PCI9118_INT_CTRL_DTRG           BIT(0)  /* ext. digital trigger */

#define START_AI_EXT    0x01    /* start measure on external trigger */
#define STOP_AI_EXT     0x02    /* stop measure on external trigger */
#define STOP_AI_INT     0x08    /* stop measure on internal trigger */

static const struct comedi_lrange pci9118_ai_range = {
        8, {
                BIP_RANGE(5),
                BIP_RANGE(2.5),
                BIP_RANGE(1.25),
                BIP_RANGE(0.625),
                UNI_RANGE(10),
                UNI_RANGE(5),
                UNI_RANGE(2.5),
                UNI_RANGE(1.25)
        }
};

static const struct comedi_lrange pci9118hg_ai_range = {
        8, {
                BIP_RANGE(5),
                BIP_RANGE(0.5),
                BIP_RANGE(0.05),
                BIP_RANGE(0.005),
                UNI_RANGE(10),
                UNI_RANGE(1),
                UNI_RANGE(0.1),
                UNI_RANGE(0.01)
        }
};

enum pci9118_boardid {
        BOARD_PCI9118DG,
        BOARD_PCI9118HG,
        BOARD_PCI9118HR,
};

struct pci9118_boardinfo {
        const char *name;
        unsigned int ai_is_16bit:1;
        unsigned int is_hg:1;
};

static const struct pci9118_boardinfo pci9118_boards[] = {
        [BOARD_PCI9118DG] = {
                .name           = "pci9118dg",
        },
        [BOARD_PCI9118HG] = {
                .name           = "pci9118hg",
                .is_hg          = 1,
        },
        [BOARD_PCI9118HR] = {
                .name           = "pci9118hr",
                .ai_is_16bit    = 1,
        },
};

struct pci9118_dmabuf {
        unsigned short *virt;   /* virtual address of buffer */
        dma_addr_t hw;          /* hardware (bus) address of buffer */
        unsigned int size;      /* size of dma buffer in bytes */
        unsigned int use_size;  /* which size we may now use for transfer */
};

struct pci9118_private {
        unsigned long iobase_a; /* base+size for AMCC chip */
        unsigned int master:1;
        unsigned int dma_doublebuf:1;
        unsigned int ai_neverending:1;
        unsigned int usedma:1;
        unsigned int usemux:1;
        unsigned char ai_ctrl;
        unsigned char int_ctrl;
        unsigned char ai_cfg;
        unsigned int ai_do;             /* what do AI? 0=nothing, 1 to 4 mode */
        unsigned int ai_n_realscanlen;  /*
                                         * what we must transfer for one
                                         * outgoing scan include front/back adds
                                         */
        unsigned int ai_act_dmapos;     /* position in actual real stream */
        unsigned int ai_add_front;      /*
                                         * how many channels we must add
                                         * before scan to satisfy S&H?
                                         */
        unsigned int ai_add_back;       /*
                                         * how many channels we must add
                                         * before scan to satisfy DMA?
                                         */
        unsigned int ai_flags;
        char ai12_startstop;            /*
                                         * measure can start/stop
                                         * on external trigger
                                         */
        unsigned int dma_actbuf;                /* which buffer is used now */
        struct pci9118_dmabuf dmabuf[2];
        int softsshdelay;               /*
                                         * >0 use software S&H,
                                         * number is requested delay in ns
                                         */
        unsigned char softsshsample;    /*
                                         * polarity of S&H signal
                                         * in sample state
                                         */
        unsigned char softsshhold;      /*
                                         * polarity of S&H signal
                                         * in hold state
                                         */
        unsigned int ai_ns_min;
};

static void pci9118_amcc_setup_dma(struct comedi_device *dev, unsigned int buf)
{
        struct pci9118_private *devpriv = dev->private;
        struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[buf];

        /* set the master write address and transfer count */
        outl(dmabuf->hw, devpriv->iobase_a + AMCC_OP_REG_MWAR);
        outl(dmabuf->use_size, devpriv->iobase_a + AMCC_OP_REG_MWTC);
}

static void pci9118_amcc_dma_ena(struct comedi_device *dev, bool enable)
{
        struct pci9118_private *devpriv = dev->private;
        unsigned int mcsr;

        mcsr = inl(devpriv->iobase_a + AMCC_OP_REG_MCSR);
        if (enable)
                mcsr |= RESET_A2P_FLAGS | A2P_HI_PRIORITY | EN_A2P_TRANSFERS;
        else
                mcsr &= ~EN_A2P_TRANSFERS;
        outl(mcsr, devpriv->iobase_a + AMCC_OP_REG_MCSR);
}

static void pci9118_amcc_int_ena(struct comedi_device *dev, bool enable)
{
        struct pci9118_private *devpriv = dev->private;
        unsigned int intcsr;

        /* enable/disable interrupt for AMCC Incoming Mailbox 4 (32-bit) */
        intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);
        if (enable)
                intcsr |= 0x1f00;
        else
                intcsr &= ~0x1f00;
        outl(intcsr, devpriv->iobase_a + AMCC_OP_REG_INTCSR);
}

static void pci9118_ai_reset_fifo(struct comedi_device *dev)
{
        /* writing any value resets the A/D FIFO */
        outl(0, dev->iobase + PCI9118_FIFO_RESET_REG);
}

static int pci9118_ai_check_chanlist(struct comedi_device *dev,
                                     struct comedi_subdevice *s,
                                     struct comedi_cmd *cmd)
{
        struct pci9118_private *devpriv = dev->private;
        unsigned int range0 = CR_RANGE(cmd->chanlist[0]);
        unsigned int aref0 = CR_AREF(cmd->chanlist[0]);
        int i;

        /* single channel scans are always ok */
        if (cmd->chanlist_len == 1)
                return 0;

        for (i = 1; i < cmd->chanlist_len; i++) {
                unsigned int chan = CR_CHAN(cmd->chanlist[i]);
                unsigned int range = CR_RANGE(cmd->chanlist[i]);
                unsigned int aref = CR_AREF(cmd->chanlist[i]);

                if (aref != aref0) {
                        dev_err(dev->class_dev,
                                "Differential and single ended inputs can't be mixed!\n");
                        return -EINVAL;
                }
                if (comedi_range_is_bipolar(s, range) !=
                    comedi_range_is_bipolar(s, range0)) {
                        dev_err(dev->class_dev,
                                "Bipolar and unipolar ranges can't be mixed!\n");
                        return -EINVAL;
                }
                if (!devpriv->usemux && aref == AREF_DIFF &&
                    (chan >= (s->n_chan / 2))) {
                        dev_err(dev->class_dev,
                                "AREF_DIFF is only available for the first 8 channels!\n");
                        return -EINVAL;
                }
        }

        return 0;
}

static void pci9118_set_chanlist(struct comedi_device *dev,
                                 struct comedi_subdevice *s,
                                 int n_chan, unsigned int *chanlist,
                                 int frontadd, int backadd)
{
        struct pci9118_private *devpriv = dev->private;
        unsigned int chan0 = CR_CHAN(chanlist[0]);
        unsigned int range0 = CR_RANGE(chanlist[0]);
        unsigned int aref0 = CR_AREF(chanlist[0]);
        unsigned int ssh = 0x00;
        unsigned int val;
        int i;

        /*
         * Configure analog input based on the first chanlist entry.
         * All entries are either unipolar or bipolar and single-ended
         * or differential.
         */
        devpriv->ai_ctrl = 0;
        if (comedi_range_is_unipolar(s, range0))
                devpriv->ai_ctrl |= PCI9118_AI_CTRL_UNIP;
        if (aref0 == AREF_DIFF)
                devpriv->ai_ctrl |= PCI9118_AI_CTRL_DIFF;
        outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);

        /* gods know why this sequence! */
        outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
        outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
        outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);

        /* insert channels for S&H */
        if (frontadd) {
                val = PCI9118_AI_CHANLIST_CHAN(chan0) |
                      PCI9118_AI_CHANLIST_RANGE(range0);
                ssh = devpriv->softsshsample;
                for (i = 0; i < frontadd; i++) {
                        outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
                        ssh = devpriv->softsshhold;
                }
        }

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

                val = PCI9118_AI_CHANLIST_CHAN(chan) |
                      PCI9118_AI_CHANLIST_RANGE(range);
                outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
        }

        /* insert channels to fit onto 32bit DMA */
        if (backadd) {
                val = PCI9118_AI_CHANLIST_CHAN(chan0) |
                      PCI9118_AI_CHANLIST_RANGE(range0);
                for (i = 0; i < backadd; i++)
                        outl(val | ssh, dev->iobase + PCI9118_AI_CHANLIST_REG);
        }
        /* close scan queue */
        outl(0, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
        /* udelay(100); important delay, or first sample will be crippled */
}

static void pci9118_ai_mode4_switch(struct comedi_device *dev,
                                    unsigned int next_buf)
{
        struct pci9118_private *devpriv = dev->private;
        struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[next_buf];

        devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG |
                          PCI9118_AI_CFG_AM;
        outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
        comedi_8254_load(dev->pacer, 0, dmabuf->hw >> 1,
                         I8254_MODE0 | I8254_BINARY);
        devpriv->ai_cfg |= PCI9118_AI_CFG_START;
        outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
}

static unsigned int pci9118_ai_samples_ready(struct comedi_device *dev,
                                             struct comedi_subdevice *s,
                                             unsigned int n_raw_samples)
{
        struct pci9118_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        unsigned int start_pos = devpriv->ai_add_front;
        unsigned int stop_pos = start_pos + cmd->chanlist_len;
        unsigned int span_len = stop_pos + devpriv->ai_add_back;
        unsigned int dma_pos = devpriv->ai_act_dmapos;
        unsigned int whole_spans, n_samples, x;

        if (span_len == cmd->chanlist_len)
                return n_raw_samples;   /* use all samples */

        /*
         * Not all samples are to be used.  Buffer contents consist of a
         * possibly non-whole number of spans and a region of each span
         * is to be used.
         *
         * Account for samples in whole number of spans.
         */
        whole_spans = n_raw_samples / span_len;
        n_samples = whole_spans * cmd->chanlist_len;
        n_raw_samples -= whole_spans * span_len;

        /*
         * Deal with remaining samples which could overlap up to two spans.
         */
        while (n_raw_samples) {
                if (dma_pos < start_pos) {
                        /* Skip samples before start position. */
                        x = start_pos - dma_pos;
                        if (x > n_raw_samples)
                                x = n_raw_samples;
                        dma_pos += x;
                        n_raw_samples -= x;
                        if (!n_raw_samples)
                                break;
                }
                if (dma_pos < stop_pos) {
                        /* Include samples before stop position. */
                        x = stop_pos - dma_pos;
                        if (x > n_raw_samples)
                                x = n_raw_samples;
                        n_samples += x;
                        dma_pos += x;
                        n_raw_samples -= x;
                }
                /* Advance to next span. */
                start_pos += span_len;
                stop_pos += span_len;
        }
        return n_samples;
}

static void pci9118_ai_dma_xfer(struct comedi_device *dev,
                                struct comedi_subdevice *s,
                                unsigned short *dma_buffer,
                                unsigned int n_raw_samples)
{
        struct pci9118_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        unsigned int start_pos = devpriv->ai_add_front;
        unsigned int stop_pos = start_pos + cmd->chanlist_len;
        unsigned int span_len = stop_pos + devpriv->ai_add_back;
        unsigned int dma_pos = devpriv->ai_act_dmapos;
        unsigned int x;

        if (span_len == cmd->chanlist_len) {
                /* All samples are to be copied. */
                comedi_buf_write_samples(s, dma_buffer, n_raw_samples);
                dma_pos += n_raw_samples;
        } else {
                /*
                 * Not all samples are to be copied.  Buffer contents consist
                 * of a possibly non-whole number of spans and a region of
                 * each span is to be copied.
                 */
                while (n_raw_samples) {
                        if (dma_pos < start_pos) {
                                /* Skip samples before start position. */
                                x = start_pos - dma_pos;
                                if (x > n_raw_samples)
                                        x = n_raw_samples;
                                dma_pos += x;
                                n_raw_samples -= x;
                                if (!n_raw_samples)
                                        break;
                        }
                        if (dma_pos < stop_pos) {
                                /* Copy samples before stop position. */
                                x = stop_pos - dma_pos;
                                if (x > n_raw_samples)
                                        x = n_raw_samples;
                                comedi_buf_write_samples(s, dma_buffer, x);
                                dma_pos += x;
                                n_raw_samples -= x;
                        }
                        /* Advance to next span. */
                        start_pos += span_len;
                        stop_pos += span_len;
                }
        }
        /* Update position in span for next time. */
        devpriv->ai_act_dmapos = dma_pos % span_len;
}

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

        if (enable)
                devpriv->int_ctrl |= PCI9118_INT_CTRL_DTRG;
        else
                devpriv->int_ctrl &= ~PCI9118_INT_CTRL_DTRG;
        outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);

        if (devpriv->int_ctrl)
                pci9118_amcc_int_ena(dev, true);
        else
                pci9118_amcc_int_ena(dev, false);
}

static void pci9118_calc_divisors(struct comedi_device *dev,
                                  struct comedi_subdevice *s,
                                  unsigned int *tim1, unsigned int *tim2,
                                  unsigned int flags, int chans,
                                  unsigned int *div1, unsigned int *div2,
                                  unsigned int chnsshfront)
{
        struct comedi_8254 *pacer = dev->pacer;
        struct comedi_cmd *cmd = &s->async->cmd;

        *div1 = *tim2 / pacer->osc_base;        /* convert timer (burst) */
        *div2 = *tim1 / pacer->osc_base;        /* scan timer */
        *div2 = *div2 / *div1;                  /* major timer is c1*c2 */
        if (*div2 < chans)
                *div2 = chans;

        *tim2 = *div1 * pacer->osc_base;        /* real convert timer */

        if (cmd->convert_src == TRIG_NOW && !chnsshfront) {
                /* use BSSH signal */
                if (*div2 < (chans + 2))
                        *div2 = chans + 2;
        }

        *tim1 = *div1 * *div2 * pacer->osc_base;
}

static void pci9118_start_pacer(struct comedi_device *dev, int mode)
{
        if (mode == 1 || mode == 2 || mode == 4)
                comedi_8254_pacer_enable(dev->pacer, 1, 2, true);
}

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

        if (devpriv->usedma)
                pci9118_amcc_dma_ena(dev, false);
        pci9118_exttrg_enable(dev, false);
        comedi_8254_pacer_enable(dev->pacer, 1, 2, false);
        /* set default config (disable burst and triggers) */
        devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
        outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
        /* reset acquisition control */
        devpriv->ai_ctrl = 0;
        outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
        outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
        /* reset scan queue */
        outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
        outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
        pci9118_ai_reset_fifo(dev);

        devpriv->int_ctrl = 0;
        outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
        pci9118_amcc_int_ena(dev, false);

        devpriv->ai_do = 0;
        devpriv->usedma = 0;

        devpriv->ai_act_dmapos = 0;
        s->async->inttrig = NULL;
        devpriv->ai_neverending = 0;
        devpriv->dma_actbuf = 0;

        return 0;
}

static void pci9118_ai_munge(struct comedi_device *dev,
                             struct comedi_subdevice *s, void *data,
                             unsigned int num_bytes,
                             unsigned int start_chan_index)
{
        struct pci9118_private *devpriv = dev->private;
        unsigned short *array = data;
        unsigned int num_samples = comedi_bytes_to_samples(s, num_bytes);
        unsigned int i;
        __be16 *barray = data;

        for (i = 0; i < num_samples; i++) {
                if (devpriv->usedma)
                        array[i] = be16_to_cpu(barray[i]);
                if (s->maxdata == 0xffff)
                        array[i] ^= 0x8000;
                else
                        array[i] = (array[i] >> 4) & 0x0fff;
        }
}

static void pci9118_ai_get_onesample(struct comedi_device *dev,
                                     struct comedi_subdevice *s)
{
        struct pci9118_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        unsigned short sampl;

        sampl = inl(dev->iobase + PCI9118_AI_FIFO_REG);

        comedi_buf_write_samples(s, &sampl, 1);

        if (!devpriv->ai_neverending) {
                if (s->async->scans_done >= cmd->stop_arg)
                        s->async->events |= COMEDI_CB_EOA;
        }
}

static void pci9118_ai_get_dma(struct comedi_device *dev,
                               struct comedi_subdevice *s)
{
        struct pci9118_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[devpriv->dma_actbuf];
        unsigned int n_all = comedi_bytes_to_samples(s, dmabuf->use_size);
        unsigned int n_valid;
        bool more_dma;

        /* determine whether more DMA buffers to do after this one */
        n_valid = pci9118_ai_samples_ready(dev, s, n_all);
        more_dma = n_valid < comedi_nsamples_left(s, n_valid + 1);

        /* switch DMA buffers and restart DMA if double buffering */
        if (more_dma && devpriv->dma_doublebuf) {
                devpriv->dma_actbuf = 1 - devpriv->dma_actbuf;
                pci9118_amcc_setup_dma(dev, devpriv->dma_actbuf);
                if (devpriv->ai_do == 4)
                        pci9118_ai_mode4_switch(dev, devpriv->dma_actbuf);
        }

        if (n_all)
                pci9118_ai_dma_xfer(dev, s, dmabuf->virt, n_all);

        if (!devpriv->ai_neverending) {
                if (s->async->scans_done >= cmd->stop_arg)
                        s->async->events |= COMEDI_CB_EOA;
        }

        if (s->async->events & COMEDI_CB_CANCEL_MASK)
                more_dma = false;

        /* restart DMA if not double buffering */
        if (more_dma && !devpriv->dma_doublebuf) {
                pci9118_amcc_setup_dma(dev, 0);
                if (devpriv->ai_do == 4)
                        pci9118_ai_mode4_switch(dev, 0);
        }
}

static irqreturn_t pci9118_interrupt(int irq, void *d)
{
        struct comedi_device *dev = d;
        struct comedi_subdevice *s = dev->read_subdev;
        struct pci9118_private *devpriv = dev->private;
        unsigned int intsrc;    /* IRQ reasons from card */
        unsigned int intcsr;    /* INT register from AMCC chip */
        unsigned int adstat;    /* STATUS register */

        if (!dev->attached)
                return IRQ_NONE;

        intsrc = inl(dev->iobase + PCI9118_INT_CTRL_REG) & 0xf;
        intcsr = inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR);

        if (!intsrc && !(intcsr & ANY_S593X_INT))
                return IRQ_NONE;

        outl(intcsr | 0x00ff0000, devpriv->iobase_a + AMCC_OP_REG_INTCSR);

        if (intcsr & MASTER_ABORT_INT) {
                dev_err(dev->class_dev, "AMCC IRQ - MASTER DMA ABORT!\n");
                s->async->events |= COMEDI_CB_ERROR;
                goto interrupt_exit;
        }

        if (intcsr & TARGET_ABORT_INT) {
                dev_err(dev->class_dev, "AMCC IRQ - TARGET DMA ABORT!\n");
                s->async->events |= COMEDI_CB_ERROR;
                goto interrupt_exit;
        }

        adstat = inl(dev->iobase + PCI9118_AI_STATUS_REG);
        if ((adstat & PCI9118_AI_STATUS_NFULL) == 0) {
                dev_err(dev->class_dev,
                        "A/D FIFO Full status (Fatal Error!)\n");
                s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
                goto interrupt_exit;
        }
        if (adstat & PCI9118_AI_STATUS_BOVER) {
                dev_err(dev->class_dev,
                        "A/D Burst Mode Overrun Status (Fatal Error!)\n");
                s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
                goto interrupt_exit;
        }
        if (adstat & PCI9118_AI_STATUS_ADOS) {
                dev_err(dev->class_dev, "A/D Over Speed Status (Warning!)\n");
                s->async->events |= COMEDI_CB_ERROR;
                goto interrupt_exit;
        }
        if (adstat & PCI9118_AI_STATUS_ADOR) {
                dev_err(dev->class_dev, "A/D Overrun Status (Fatal Error!)\n");
                s->async->events |= COMEDI_CB_ERROR | COMEDI_CB_OVERFLOW;
                goto interrupt_exit;
        }

        if (!devpriv->ai_do)
                return IRQ_HANDLED;

        if (devpriv->ai12_startstop) {
                if ((adstat & PCI9118_AI_STATUS_DTH) &&
                    (intsrc & PCI9118_INT_CTRL_DTRG)) {
                        /* start/stop of measure */
                        if (devpriv->ai12_startstop & START_AI_EXT) {
                                /* deactivate EXT trigger */
                                devpriv->ai12_startstop &= ~START_AI_EXT;
                                if (!(devpriv->ai12_startstop & STOP_AI_EXT))
                                        pci9118_exttrg_enable(dev, false);

                                /* start pacer */
                                pci9118_start_pacer(dev, devpriv->ai_do);
                                outl(devpriv->ai_ctrl,
                                     dev->iobase + PCI9118_AI_CTRL_REG);
                        } else if (devpriv->ai12_startstop & STOP_AI_EXT) {
                                /* deactivate EXT trigger */
                                devpriv->ai12_startstop &= ~STOP_AI_EXT;
                                pci9118_exttrg_enable(dev, false);

                                /* on next interrupt measure will stop */
                                devpriv->ai_neverending = 0;
                        }
                }
        }

        if (devpriv->usedma)
                pci9118_ai_get_dma(dev, s);
        else
                pci9118_ai_get_onesample(dev, s);

interrupt_exit:
        comedi_handle_events(dev, s);
        return IRQ_HANDLED;
}

static void pci9118_ai_cmd_start(struct comedi_device *dev)
{
        struct pci9118_private *devpriv = dev->private;

        outl(devpriv->int_ctrl, dev->iobase + PCI9118_INT_CTRL_REG);
        outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
        if (devpriv->ai_do != 3) {
                pci9118_start_pacer(dev, devpriv->ai_do);
                devpriv->ai_ctrl |= PCI9118_AI_CTRL_SOFTG;
        }
        outl(devpriv->ai_ctrl, dev->iobase + PCI9118_AI_CTRL_REG);
}

static int pci9118_ai_inttrig(struct comedi_device *dev,
                              struct comedi_subdevice *s,
                              unsigned int trig_num)
{
        struct comedi_cmd *cmd = &s->async->cmd;

        if (trig_num != cmd->start_arg)
                return -EINVAL;

        s->async->inttrig = NULL;
        pci9118_ai_cmd_start(dev);

        return 1;
}

static int pci9118_ai_setup_dma(struct comedi_device *dev,
                                struct comedi_subdevice *s)
{
        struct pci9118_private *devpriv = dev->private;
        struct comedi_cmd *cmd = &s->async->cmd;
        struct pci9118_dmabuf *dmabuf0 = &devpriv->dmabuf[0];
        struct pci9118_dmabuf *dmabuf1 = &devpriv->dmabuf[1];
        unsigned int dmalen0 = dmabuf0->size;
        unsigned int dmalen1 = dmabuf1->size;
        unsigned int scan_bytes = devpriv->ai_n_realscanlen *
                                  comedi_bytes_per_sample(s);

        /* isn't output buff smaller that our DMA buff? */
        if (dmalen0 > s->async->prealloc_bufsz) {
                /* align to 32bit down */
                dmalen0 = s->async->prealloc_bufsz & ~3L;
        }
        if (dmalen1 > s->async->prealloc_bufsz) {
                /* align to 32bit down */
                dmalen1 = s->async->prealloc_bufsz & ~3L;
        }

        /* we want wake up every scan? */
        if (devpriv->ai_flags & CMDF_WAKE_EOS) {
                if (dmalen0 < scan_bytes) {
                        /* uff, too short DMA buffer, disable EOS support! */
                        devpriv->ai_flags &= (~CMDF_WAKE_EOS);
                        dev_info(dev->class_dev,
                                 "WAR: DMA0 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n",
                                  dmalen0, scan_bytes);
                } else {
                        /* short first DMA buffer to one scan */
                        dmalen0 = scan_bytes;
                        if (dmalen0 < 4) {
                                dev_info(dev->class_dev,
                                         "ERR: DMA0 buf len bug? (%d<4)\n",
                                         dmalen0);
                                dmalen0 = 4;
                        }
                }
        }
        if (devpriv->ai_flags & CMDF_WAKE_EOS) {
                if (dmalen1 < scan_bytes) {
                        /* uff, too short DMA buffer, disable EOS support! */
                        devpriv->ai_flags &= (~CMDF_WAKE_EOS);
                        dev_info(dev->class_dev,
                                 "WAR: DMA1 buf too short, can't support CMDF_WAKE_EOS (%d<%d)\n",
                                 dmalen1, scan_bytes);
                } else {
                        /* short second DMA buffer to one scan */
                        dmalen1 = scan_bytes;
                        if (dmalen1 < 4) {
                                dev_info(dev->class_dev,
                                         "ERR: DMA1 buf len bug? (%d<4)\n",
                                         dmalen1);
                                dmalen1 = 4;
                        }
                }
        }

        /* transfer without CMDF_WAKE_EOS */
        if (!(devpriv->ai_flags & CMDF_WAKE_EOS)) {
                unsigned int tmp;

                /* if it's possible then align DMA buffers to length of scan */
                tmp = dmalen0;
                dmalen0 = (dmalen0 / scan_bytes) * scan_bytes;
                dmalen0 &= ~3L;
                if (!dmalen0)
                        dmalen0 = tmp;  /* uff. very long scan? */
                tmp = dmalen1;
                dmalen1 = (dmalen1 / scan_bytes) * scan_bytes;
                dmalen1 &= ~3L;
                if (!dmalen1)
                        dmalen1 = tmp;  /* uff. very long scan? */
                /*
                 * if measure isn't neverending then test, if it fits whole
                 * into one or two DMA buffers
                 */
                if (!devpriv->ai_neverending) {
                        unsigned long long scanlen;

                        scanlen = (unsigned long long)scan_bytes *
                                  cmd->stop_arg;

                        /* fits whole measure into one DMA buffer? */
                        if (dmalen0 > scanlen) {
                                dmalen0 = scanlen;
                                dmalen0 &= ~3L;
                        } else {
                                /* fits whole measure into two DMA buffer? */
                                if (dmalen1 > (scanlen - dmalen0)) {
                                        dmalen1 = scanlen - dmalen0;
                                        dmalen1 &= ~3L;
                                }
                        }
                }
        }

        /* these DMA buffer size will be used */
        devpriv->dma_actbuf = 0;
        dmabuf0->use_size = dmalen0;
        dmabuf1->use_size = dmalen1;

        pci9118_amcc_dma_ena(dev, false);
        pci9118_amcc_setup_dma(dev, 0);
        /* init DMA transfer */
        outl(0x00000000 | AINT_WRITE_COMPL,
             devpriv->iobase_a + AMCC_OP_REG_INTCSR);
/* outl(0x02000000|AINT_WRITE_COMPL, devpriv->iobase_a+AMCC_OP_REG_INTCSR); */
        pci9118_amcc_dma_ena(dev, true);
        outl(inl(devpriv->iobase_a + AMCC_OP_REG_INTCSR) | EN_A2P_TRANSFERS,
             devpriv->iobase_a + AMCC_OP_REG_INTCSR);
                                                /* allow bus mastering */

        return 0;
}

static int pci9118_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
{
        struct pci9118_private *devpriv = dev->private;
        struct comedi_8254 *pacer = dev->pacer;
        struct comedi_cmd *cmd = &s->async->cmd;
        unsigned int addchans = 0;
        unsigned int scanlen;

        devpriv->ai12_startstop = 0;
        devpriv->ai_flags = cmd->flags;
        devpriv->ai_add_front = 0;
        devpriv->ai_add_back = 0;

        /* prepare for start/stop conditions */
        if (cmd->start_src == TRIG_EXT)
                devpriv->ai12_startstop |= START_AI_EXT;
        if (cmd->stop_src == TRIG_EXT) {
                devpriv->ai_neverending = 1;
                devpriv->ai12_startstop |= STOP_AI_EXT;
        }
        if (cmd->stop_src == TRIG_NONE)
                devpriv->ai_neverending = 1;
        if (cmd->stop_src == TRIG_COUNT)
                devpriv->ai_neverending = 0;

        /*
         * use additional sample at end of every scan
         * to satisty DMA 32 bit transfer?
         */
        devpriv->ai_add_front = 0;
        devpriv->ai_add_back = 0;
        if (devpriv->master) {
                devpriv->usedma = 1;
                if ((cmd->flags & CMDF_WAKE_EOS) &&
                    (cmd->scan_end_arg == 1)) {
                        if (cmd->convert_src == TRIG_NOW)
                                devpriv->ai_add_back = 1;
                        if (cmd->convert_src == TRIG_TIMER) {
                                devpriv->usedma = 0;
                                        /*
                                         * use INT transfer if scanlist
                                         * have only one channel
                                         */
                        }
                }
                if ((cmd->flags & CMDF_WAKE_EOS) &&
                    (cmd->scan_end_arg & 1) &&
                    (cmd->scan_end_arg > 1)) {
                        if (cmd->scan_begin_src == TRIG_FOLLOW) {
                                devpriv->usedma = 0;
                                /*
                                 * XXX maybe can be corrected to use 16 bit DMA
                                 */
                        } else {        /*
                                         * well, we must insert one sample
                                         * to end of EOS to meet 32 bit transfer
                                         */
                                devpriv->ai_add_back = 1;
                        }
                }
        } else {        /* interrupt transfer don't need any correction */
                devpriv->usedma = 0;
        }

        /*
         * we need software S&H signal?
         * It adds two samples before every scan as minimum
         */
        if (cmd->convert_src == TRIG_NOW && devpriv->softsshdelay) {
                devpriv->ai_add_front = 2;
                if ((devpriv->usedma == 1) && (devpriv->ai_add_back == 1)) {
                                                        /* move it to front */
                        devpriv->ai_add_front++;
                        devpriv->ai_add_back = 0;
                }
                if (cmd->convert_arg < devpriv->ai_ns_min)
                        cmd->convert_arg = devpriv->ai_ns_min;
                addchans = devpriv->softsshdelay / cmd->convert_arg;
                if (devpriv->softsshdelay % cmd->convert_arg)
                        addchans++;
                if (addchans > (devpriv->ai_add_front - 1)) {
                                                        /* uff, still short */
                        devpriv->ai_add_front = addchans + 1;
                        if (devpriv->usedma == 1)
                                if ((devpriv->ai_add_front +
                                     cmd->chanlist_len +
                                     devpriv->ai_add_back) & 1)
                                        devpriv->ai_add_front++;
                                                        /* round up to 32 bit */
                }
        }
        /* well, we now know what must be all added */
        scanlen = devpriv->ai_add_front + cmd->chanlist_len +
                  devpriv->ai_add_back;
        /*
         * what we must take from card in real to have cmd->scan_end_arg
         * on output?
         */
        devpriv->ai_n_realscanlen = scanlen *
                                    (cmd->scan_end_arg / cmd->chanlist_len);

        if (scanlen > s->len_chanlist) {
                dev_err(dev->class_dev,
                        "range/channel list is too long for actual configuration!\n");
                return -EINVAL;
        }

        /*
         * Configure analog input and load the chanlist.
         * The acquisition control bits are enabled later.
         */
        pci9118_set_chanlist(dev, s, cmd->chanlist_len, cmd->chanlist,
                             devpriv->ai_add_front, devpriv->ai_add_back);

        /* Determine acquisition mode and calculate timing */
        devpriv->ai_do = 0;
        if (cmd->scan_begin_src != TRIG_TIMER &&
            cmd->convert_src == TRIG_TIMER) {
                /* cascaded timers 1 and 2 are used for convert timing */
                if (cmd->scan_begin_src == TRIG_EXT)
                        devpriv->ai_do = 4;
                else
                        devpriv->ai_do = 1;

                comedi_8254_cascade_ns_to_timer(pacer, &cmd->convert_arg,
                                                devpriv->ai_flags &
                                                CMDF_ROUND_NEAREST);
                comedi_8254_update_divisors(pacer);

                devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR;

                if (!devpriv->usedma) {
                        devpriv->ai_ctrl |= PCI9118_AI_CTRL_INT;
                        devpriv->int_ctrl |= PCI9118_INT_CTRL_TIMER;
                }

                if (cmd->scan_begin_src == TRIG_EXT) {
                        struct pci9118_dmabuf *dmabuf = &devpriv->dmabuf[0];

                        devpriv->ai_cfg |= PCI9118_AI_CFG_AM;
                        outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
                        comedi_8254_load(pacer, 0, dmabuf->hw >> 1,
                                         I8254_MODE0 | I8254_BINARY);
                        devpriv->ai_cfg |= PCI9118_AI_CFG_START;
                }
        }

        if (cmd->scan_begin_src == TRIG_TIMER &&
            cmd->convert_src != TRIG_EXT) {
                if (!devpriv->usedma) {
                        dev_err(dev->class_dev,
                                "cmd->scan_begin_src=TRIG_TIMER works only with bus mastering!\n");
                        return -EIO;
                }

                /* double timed action */
                devpriv->ai_do = 2;

                pci9118_calc_divisors(dev, s,
                                      &cmd->scan_begin_arg, &cmd->convert_arg,
                                      devpriv->ai_flags,
                                      devpriv->ai_n_realscanlen,
                                      &pacer->divisor1,
                                      &pacer->divisor2,
                                      devpriv->ai_add_front);

                devpriv->ai_ctrl |= PCI9118_AI_CTRL_TMRTR;
                devpriv->ai_cfg |= PCI9118_AI_CFG_BM | PCI9118_AI_CFG_BS;
                if (cmd->convert_src == TRIG_NOW && !devpriv->softsshdelay)
                        devpriv->ai_cfg |= PCI9118_AI_CFG_BSSH;
                outl(devpriv->ai_n_realscanlen,
                     dev->iobase + PCI9118_AI_BURST_NUM_REG);
        }

        if (cmd->scan_begin_src == TRIG_FOLLOW &&
            cmd->convert_src == TRIG_EXT) {
                /* external trigger conversion */
                devpriv->ai_do = 3;

                devpriv->ai_ctrl |= PCI9118_AI_CTRL_EXTM;
        }

        if (devpriv->ai_do == 0) {
                dev_err(dev->class_dev,
                        "Unable to determine acquisition mode! BUG in (*do_cmdtest)?\n");
                return -EINVAL;
        }

        if (devpriv->usedma)
                devpriv->ai_ctrl |= PCI9118_AI_CTRL_DMA;

        /* set default config (disable burst and triggers) */
        devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
        outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);
        udelay(1);
        pci9118_ai_reset_fifo(dev);

        /* clear A/D and INT status registers */
        inl(dev->iobase + PCI9118_AI_STATUS_REG);
        inl(dev->iobase + PCI9118_INT_CTRL_REG);

        devpriv->ai_act_dmapos = 0;

        if (devpriv->usedma) {
                pci9118_ai_setup_dma(dev, s);

                outl(0x02000000 | AINT_WRITE_COMPL,
                     devpriv->iobase_a + AMCC_OP_REG_INTCSR);
        } else {
                pci9118_amcc_int_ena(dev, true);
        }

        /* start async command now or wait for internal trigger */
        if (cmd->start_src == TRIG_NOW)
                pci9118_ai_cmd_start(dev);
        else if (cmd->start_src == TRIG_INT)
                s->async->inttrig = pci9118_ai_inttrig;

        /* enable external trigger for command start/stop */
        if (cmd->start_src == TRIG_EXT || cmd->stop_src == TRIG_EXT)
                pci9118_exttrg_enable(dev, true);

        return 0;
}

static int pci9118_ai_cmdtest(struct comedi_device *dev,
                              struct comedi_subdevice *s,
                              struct comedi_cmd *cmd)
{
        struct pci9118_private *devpriv = dev->private;
        int err = 0;
        unsigned int flags;
        unsigned int arg;

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

        err |= comedi_check_trigger_src(&cmd->start_src,
                                        TRIG_NOW | TRIG_EXT | TRIG_INT);

        flags = TRIG_FOLLOW;
        if (devpriv->master)
                flags |= TRIG_TIMER | TRIG_EXT;
        err |= comedi_check_trigger_src(&cmd->scan_begin_src, flags);

        flags = TRIG_TIMER | TRIG_EXT;
        if (devpriv->master)
                flags |= TRIG_NOW;
        err |= comedi_check_trigger_src(&cmd->convert_src, flags);

        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
        err |= comedi_check_trigger_src(&cmd->stop_src,
                                        TRIG_COUNT | TRIG_NONE | TRIG_EXT);

        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->convert_src);
        err |= comedi_check_trigger_is_unique(cmd->stop_src);

        /* Step 2b : and mutually compatible */

        if (cmd->start_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
                err |= -EINVAL;

        if ((cmd->scan_begin_src & (TRIG_TIMER | TRIG_EXT)) &&
            (!(cmd->convert_src & (TRIG_TIMER | TRIG_NOW))))
                err |= -EINVAL;

        if ((cmd->scan_begin_src == TRIG_FOLLOW) &&
            (!(cmd->convert_src & (TRIG_TIMER | TRIG_EXT))))
                err |= -EINVAL;

        if (cmd->stop_src == TRIG_EXT && cmd->scan_begin_src == TRIG_EXT)
                err |= -EINVAL;

        if (err)
                return 2;

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

        switch (cmd->start_src) {
        case TRIG_NOW:
        case TRIG_EXT:
                err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
                break;
        case TRIG_INT:
                /* start_arg is the internal trigger (any value) */
                break;
        }

        if (cmd->scan_begin_src & (TRIG_FOLLOW | TRIG_EXT))
                err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);

        if ((cmd->scan_begin_src == TRIG_TIMER) &&
            (cmd->convert_src == TRIG_TIMER) && (cmd->scan_end_arg == 1)) {
                cmd->scan_begin_src = TRIG_FOLLOW;
                cmd->convert_arg = cmd->scan_begin_arg;
                cmd->scan_begin_arg = 0;
        }

        if (cmd->scan_begin_src == TRIG_TIMER) {
                err |= comedi_check_trigger_arg_min(&cmd->scan_begin_arg,
                                                    devpriv->ai_ns_min);
        }

        if (cmd->scan_begin_src == TRIG_EXT) {
                if (cmd->scan_begin_arg) {
                        cmd->scan_begin_arg = 0;
                        err |= -EINVAL;
                        err |= comedi_check_trigger_arg_max(&cmd->scan_end_arg,
                                                            65535);
                }
        }

        if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
                err |= comedi_check_trigger_arg_min(&cmd->convert_arg,
                                                    devpriv->ai_ns_min);
        }

        if (cmd->convert_src == TRIG_EXT)
                err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);

        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);

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

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

        if ((cmd->scan_end_arg % cmd->chanlist_len)) {
                cmd->scan_end_arg =
                    cmd->chanlist_len * (cmd->scan_end_arg / cmd->chanlist_len);
                err |= -EINVAL;
        }

        if (err)
                return 3;

        /* step 4: fix up any arguments */

        if (cmd->scan_begin_src == TRIG_TIMER) {
                arg = cmd->scan_begin_arg;
                comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
                err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
        }

        if (cmd->convert_src & (TRIG_TIMER | TRIG_NOW)) {
                arg = cmd->convert_arg;
                comedi_8254_cascade_ns_to_timer(dev->pacer, &arg, cmd->flags);
                err |= comedi_check_trigger_arg_is(&cmd->convert_arg, arg);

                if (cmd->scan_begin_src == TRIG_TIMER &&
                    cmd->convert_src == TRIG_NOW) {
                        if (cmd->convert_arg == 0) {
                                arg = devpriv->ai_ns_min *
                                      (cmd->scan_end_arg + 2);
                        } else {
                                arg = cmd->convert_arg * cmd->chanlist_len;
                        }
                        err |= comedi_check_trigger_arg_min(
                                &cmd->scan_begin_arg, arg);
                }
        }

        if (err)
                return 4;

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

        if (cmd->chanlist)
                err |= pci9118_ai_check_chanlist(dev, s, cmd);

        if (err)
                return 5;

        return 0;
}

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

        status = inl(dev->iobase + PCI9118_AI_STATUS_REG);
        if (status & PCI9118_AI_STATUS_ADRDY)
                return 0;
        return -EBUSY;
}

static void pci9118_ai_start_conv(struct comedi_device *dev)
{
        /* writing any value triggers an A/D conversion */
        outl(0, dev->iobase + PCI9118_SOFTTRG_REG);
}

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

       /*
        * Configure analog input based on the chanspec.
        * Acqusition is software controlled without interrupts.
        */
        pci9118_set_chanlist(dev, s, 1, &insn->chanspec, 0, 0);

        /* set default config (disable burst and triggers) */
        devpriv->ai_cfg = PCI9118_AI_CFG_PDTRG | PCI9118_AI_CFG_PETRG;
        outl(devpriv->ai_cfg, dev->iobase + PCI9118_AI_CFG_REG);

        pci9118_ai_reset_fifo(dev);

        for (i = 0; i < insn->n; i++) {
                pci9118_ai_start_conv(dev);

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

                val = inl(dev->iobase + PCI9118_AI_FIFO_REG);
                if (s->maxdata == 0xffff)
                        data[i] = (val & 0xffff) ^ 0x8000;
                else
                        data[i] = (val >> 4) & 0xfff;
        }

        return insn->n;
}

static int pci9118_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);
        unsigned int val = s->readback[chan];
        int i;

        for (i = 0; i < insn->n; i++) {
                val = data[i];
                outl(val, dev->iobase + PCI9118_AO_REG(chan));
        }
        s->readback[chan] = val;

        return insn->n;
}

static int pci9118_di_insn_bits(struct comedi_device *dev,
                                struct comedi_subdevice *s,
                                struct comedi_insn *insn,
                                unsigned int *data)
{
        /*
         * The digital inputs and outputs share the read register.
         * bits [7:4] are the digital outputs
         * bits [3:0] are the digital inputs
         */
        data[1] = inl(dev->iobase + PCI9118_DIO_REG) & 0xf;

        return insn->n;
}

static int pci9118_do_insn_bits(struct comedi_device *dev,
                                struct comedi_subdevice *s,
                                struct comedi_insn *insn,
                                unsigned int *data)
{
        /*
         * The digital outputs are set with the same register that
         * the digital inputs and outputs are read from. But the
         * outputs are set with bits [3:0] so we can simply write
         * the s->state to set them.
         */
        if (comedi_dio_update_state(s, data))
                outl(s->state, dev->iobase + PCI9118_DIO_REG);

        data[1] = s->state;

        return insn->n;
}

static void pci9118_reset(struct comedi_device *dev)
{
        /* reset analog input subsystem */
        outl(0, dev->iobase + PCI9118_INT_CTRL_REG);
        outl(0, dev->iobase + PCI9118_AI_CTRL_REG);
        outl(0, dev->iobase + PCI9118_AI_CFG_REG);
        pci9118_ai_reset_fifo(dev);

        /* clear any pending interrupts and status */
        inl(dev->iobase + PCI9118_INT_CTRL_REG);
        inl(dev->iobase + PCI9118_AI_STATUS_REG);

        /* reset DMA and scan queue */
        outl(0, dev->iobase + PCI9118_AI_BURST_NUM_REG);
        outl(1, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);
        outl(2, dev->iobase + PCI9118_AI_AUTOSCAN_MODE_REG);

        /* reset analog outputs to 0V */
        outl(2047, dev->iobase + PCI9118_AO_REG(0));
        outl(2047, dev->iobase + PCI9118_AO_REG(1));
}

static struct pci_dev *pci9118_find_pci(struct comedi_device *dev,
                                        struct comedi_devconfig *it)
{
        struct pci_dev *pcidev = NULL;
        int bus = it->options[0];
        int slot = it->options[1];

        for_each_pci_dev(pcidev) {
                if (pcidev->vendor != PCI_VENDOR_ID_AMCC)
                        continue;
                if (pcidev->device != 0x80d9)
                        continue;
                if (bus || slot) {
                        /* requested particular bus/slot */
                        if (pcidev->bus->number != bus ||
                            PCI_SLOT(pcidev->devfn) != slot)
                                continue;
                }
                return pcidev;
        }
        dev_err(dev->class_dev,
                "no supported board found! (req. bus/slot : %d/%d)\n",
                bus, slot);
        return NULL;
}

static void pci9118_alloc_dma(struct comedi_device *dev)
{
        struct pci9118_private *devpriv = dev->private;
        struct pci9118_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->master = 1;
                if (i == 1)
                        devpriv->dma_doublebuf = 1;
        }
}

static void pci9118_free_dma(struct comedi_device *dev)
{
        struct pci9118_private *devpriv = dev->private;
        struct pci9118_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 int pci9118_common_attach(struct comedi_device *dev,
                                 int ext_mux, int softsshdelay)
{
        const struct pci9118_boardinfo *board = dev->board_ptr;
        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
        struct pci9118_private *devpriv;
        struct comedi_subdevice *s;
        int ret;
        int i;
        u16 u16w;

        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
        if (!devpriv)
                return -ENOMEM;

        ret = comedi_pci_enable(dev);
        if (ret)
                return ret;
        pci_set_master(pcidev);

        devpriv->iobase_a = pci_resource_start(pcidev, 0);
        dev->iobase = pci_resource_start(pcidev, 2);

        dev->pacer = comedi_8254_io_alloc(dev->iobase + PCI9118_TIMER_BASE,
                                          I8254_OSC_BASE_4MHZ, I8254_IO32, 0);
        if (IS_ERR(dev->pacer))
                return PTR_ERR(dev->pacer);

        pci9118_reset(dev);

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

                        pci9118_alloc_dma(dev);
                }
        }

        if (ext_mux > 0) {
                if (ext_mux > 256)
                        ext_mux = 256;  /* max 256 channels! */
                if (softsshdelay > 0)
                        if (ext_mux > 128)
                                ext_mux = 128;
                devpriv->usemux = 1;
        } else {
                devpriv->usemux = 0;
        }

        if (softsshdelay < 0) {
                /* select sample&hold signal polarity */
                devpriv->softsshdelay = -softsshdelay;
                devpriv->softsshsample = 0x80;
                devpriv->softsshhold = 0x00;
        } else {
                devpriv->softsshdelay = softsshdelay;
                devpriv->softsshsample = 0x00;
                devpriv->softsshhold = 0x80;
        }

        pci_read_config_word(pcidev, PCI_COMMAND, &u16w);
        pci_write_config_word(pcidev, PCI_COMMAND, u16w | 64);
                                /* Enable parity check for parity error */

        ret = comedi_alloc_subdevices(dev, 4);
        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       = (devpriv->usemux) ? ext_mux : 16;
        s->maxdata      = board->ai_is_16bit ? 0xffff : 0x0fff;
        s->range_table  = board->is_hg ? &pci9118hg_ai_range
                                       : &pci9118_ai_range;
        s->insn_read    = pci9118_ai_insn_read;
        if (dev->irq) {
                dev->read_subdev = s;
                s->subdev_flags |= SDF_CMD_READ;
                s->len_chanlist = 255;
                s->do_cmdtest   = pci9118_ai_cmdtest;
                s->do_cmd       = pci9118_ai_cmd;
                s->cancel       = pci9118_ai_cancel;
                s->munge        = pci9118_ai_munge;
        }

        if (s->maxdata == 0xffff) {
                /*
                 * 16-bit samples are from an ADS7805 A/D converter.
                 * Minimum sampling rate is 10us.
                 */
                devpriv->ai_ns_min = 10000;
        } else {
                /*
                 * 12-bit samples are from an ADS7800 A/D converter.
                 * Minimum sampling rate is 3us.
                 */
                devpriv->ai_ns_min = 3000;
        }

        /* Analog Output subdevice */
        s = &dev->subdevices[1];
        s->type         = COMEDI_SUBD_AO;
        s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
        s->n_chan       = 2;
        s->maxdata      = 0x0fff;
        s->range_table  = &range_bipolar10;
        s->insn_write   = pci9118_ao_insn_write;

        ret = comedi_alloc_subdev_readback(s);
        if (ret)
                return ret;

        /* the analog outputs were reset to 0V, make the readback match */
        for (i = 0; i < s->n_chan; i++)
                s->readback[i] = 2047;

        /* 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    = pci9118_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    = pci9118_do_insn_bits;

        /* get the current state of the digital outputs */
        s->state = inl(dev->iobase + PCI9118_DIO_REG) >> 4;

        return 0;
}

static int pci9118_attach(struct comedi_device *dev,
                          struct comedi_devconfig *it)
{
        struct pci_dev *pcidev;
        int ext_mux, softsshdelay;

        ext_mux = it->options[2];
        softsshdelay = it->options[4];

        pcidev = pci9118_find_pci(dev, it);
        if (!pcidev)
                return -EIO;
        comedi_set_hw_dev(dev, &pcidev->dev);

        return pci9118_common_attach(dev, ext_mux, softsshdelay);
}

static int pci9118_auto_attach(struct comedi_device *dev,
                               unsigned long context)
{
        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
        const struct pci9118_boardinfo *board = NULL;

        if (context < ARRAY_SIZE(pci9118_boards))
                board = &pci9118_boards[context];
        if (!board)
                return -ENODEV;
        dev->board_ptr = board;
        dev->board_name = board->name;

        /*
         * Need to 'get' the PCI device to match the 'put' in pci9118_detach().
         * (The 'put' also matches the implicit 'get' by pci9118_find_pci().)
         */
        pci_dev_get(pcidev);
        /* no external mux, no sample-hold delay */
        return pci9118_common_attach(dev, 0, 0);
}

static void pci9118_detach(struct comedi_device *dev)
{
        struct pci_dev *pcidev = comedi_to_pci_dev(dev);

        if (dev->iobase)
                pci9118_reset(dev);
        comedi_pci_detach(dev);
        pci9118_free_dma(dev);
        pci_dev_put(pcidev);
}

static struct comedi_driver adl_pci9118_driver = {
        .driver_name    = "adl_pci9118",
        .module         = THIS_MODULE,
        .attach         = pci9118_attach,
        .auto_attach    = pci9118_auto_attach,
        .detach         = pci9118_detach,
        .num_names      = ARRAY_SIZE(pci9118_boards),
        .board_name     = &pci9118_boards[0].name,
        .offset         = sizeof(struct pci9118_boardinfo),
};

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

/* FIXME: All the supported board types have the same device ID! */
static const struct pci_device_id adl_pci9118_pci_table[] = {
        { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118DG },
/*      { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HG }, */
/*      { PCI_VDEVICE(AMCC, 0x80d9), BOARD_PCI9118HR }, */
        { 0 }
};
MODULE_DEVICE_TABLE(pci, adl_pci9118_pci_table);

static struct pci_driver adl_pci9118_pci_driver = {
        .name           = "adl_pci9118",
        .id_table       = adl_pci9118_pci_table,
        .probe          = adl_pci9118_pci_probe,
        .remove         = comedi_pci_auto_unconfig,
};
module_comedi_pci_driver(adl_pci9118_driver, adl_pci9118_pci_driver);

MODULE_AUTHOR("Comedi https://www.comedi.org");
MODULE_DESCRIPTION("Comedi low-level driver");
MODULE_LICENSE("GPL");