root/drivers/comedi/drivers/s526.c
// SPDX-License-Identifier: GPL-2.0+
/*
 * s526.c
 * Sensoray s526 Comedi driver
 *
 * COMEDI - Linux Control and Measurement Device Interface
 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
 */

/*
 * Driver: s526
 * Description: Sensoray 526 driver
 * Devices: [Sensoray] 526 (s526)
 * Author: Richie
 *         Everett Wang <everett.wang@everteq.com>
 * Updated: Thu, 14 Sep. 2006
 * Status: experimental
 *
 * Encoder works
 * Analog input works
 * Analog output works
 * PWM output works
 * Commands are not supported yet.
 *
 * Configuration Options:
 *   [0] - I/O port base address
 */

#include <linux/module.h>
#include <linux/comedi/comedidev.h>

/*
 * Register I/O map
 */
#define S526_TIMER_REG          0x00
#define S526_TIMER_LOAD(x)      (((x) & 0xff) << 8)
#define S526_TIMER_MODE         ((x) << 1)
#define S526_TIMER_MANUAL       S526_TIMER_MODE(0)
#define S526_TIMER_AUTO         S526_TIMER_MODE(1)
#define S526_TIMER_RESTART      BIT(0)
#define S526_WDOG_REG           0x02
#define S526_WDOG_INVERTED      BIT(4)
#define S526_WDOG_ENA           BIT(3)
#define S526_WDOG_INTERVAL(x)   (((x) & 0x7) << 0)
#define S526_AO_CTRL_REG        0x04
#define S526_AO_CTRL_RESET      BIT(3)
#define S526_AO_CTRL_CHAN(x)    (((x) & 0x3) << 1)
#define S526_AO_CTRL_START      BIT(0)
#define S526_AI_CTRL_REG        0x06
#define S526_AI_CTRL_DELAY      BIT(15)
#define S526_AI_CTRL_CONV(x)    (1 << (5 + ((x) & 0x9)))
#define S526_AI_CTRL_READ(x)    (((x) & 0xf) << 1)
#define S526_AI_CTRL_START      BIT(0)
#define S526_AO_REG             0x08
#define S526_AI_REG             0x08
#define S526_DIO_CTRL_REG       0x0a
#define S526_DIO_CTRL_DIO3_NEG  BIT(15) /* irq on DIO3 neg/pos edge */
#define S526_DIO_CTRL_DIO2_NEG  BIT(14) /* irq on DIO2 neg/pos edge */
#define S526_DIO_CTRL_DIO1_NEG  BIT(13) /* irq on DIO1 neg/pos edge */
#define S526_DIO_CTRL_DIO0_NEG  BIT(12) /* irq on DIO0 neg/pos edge */
#define S526_DIO_CTRL_GRP2_OUT  BIT(11)
#define S526_DIO_CTRL_GRP1_OUT  BIT(10)
#define S526_DIO_CTRL_GRP2_NEG  BIT(8)  /* irq on DIO[4-7] neg/pos edge */
#define S526_INT_ENA_REG        0x0c
#define S526_INT_STATUS_REG     0x0e
#define S526_INT_DIO(x)         BIT(8 + ((x) & 0x7))
#define S526_INT_EEPROM         BIT(7)  /* status only */
#define S526_INT_CNTR(x)        BIT(3 + (3 - ((x) & 0x3)))
#define S526_INT_AI             BIT(2)
#define S526_INT_AO             BIT(1)
#define S526_INT_TIMER          BIT(0)
#define S526_MISC_REG           0x10
#define S526_MISC_LED_OFF       BIT(0)
#define S526_GPCT_LSB_REG(x)    (0x12 + ((x) * 8))
#define S526_GPCT_MSB_REG(x)    (0x14 + ((x) * 8))
#define S526_GPCT_MODE_REG(x)   (0x16 + ((x) * 8))
#define S526_GPCT_MODE_COUT_SRC(x)      ((x) << 0)
#define S526_GPCT_MODE_COUT_SRC_MASK    S526_GPCT_MODE_COUT_SRC(0x1)
#define S526_GPCT_MODE_COUT_SRC_RCAP    S526_GPCT_MODE_COUT_SRC(0)
#define S526_GPCT_MODE_COUT_SRC_RTGL    S526_GPCT_MODE_COUT_SRC(1)
#define S526_GPCT_MODE_COUT_POL(x)      ((x) << 1)
#define S526_GPCT_MODE_COUT_POL_MASK    S526_GPCT_MODE_COUT_POL(0x1)
#define S526_GPCT_MODE_COUT_POL_NORM    S526_GPCT_MODE_COUT_POL(0)
#define S526_GPCT_MODE_COUT_POL_INV     S526_GPCT_MODE_COUT_POL(1)
#define S526_GPCT_MODE_AUTOLOAD(x)      ((x) << 2)
#define S526_GPCT_MODE_AUTOLOAD_MASK    S526_GPCT_MODE_AUTOLOAD(0x7)
#define S526_GPCT_MODE_AUTOLOAD_NONE    S526_GPCT_MODE_AUTOLOAD(0)
/* these 3 bits can be OR'ed */
#define S526_GPCT_MODE_AUTOLOAD_RO      S526_GPCT_MODE_AUTOLOAD(0x1)
#define S526_GPCT_MODE_AUTOLOAD_IXFALL  S526_GPCT_MODE_AUTOLOAD(0x2)
#define S526_GPCT_MODE_AUTOLOAD_IXRISE  S526_GPCT_MODE_AUTOLOAD(0x4)
#define S526_GPCT_MODE_HWCTEN_SRC(x)    ((x) << 5)
#define S526_GPCT_MODE_HWCTEN_SRC_MASK  S526_GPCT_MODE_HWCTEN_SRC(0x3)
#define S526_GPCT_MODE_HWCTEN_SRC_CEN   S526_GPCT_MODE_HWCTEN_SRC(0)
#define S526_GPCT_MODE_HWCTEN_SRC_IX    S526_GPCT_MODE_HWCTEN_SRC(1)
#define S526_GPCT_MODE_HWCTEN_SRC_IXRF  S526_GPCT_MODE_HWCTEN_SRC(2)
#define S526_GPCT_MODE_HWCTEN_SRC_NRCAP S526_GPCT_MODE_HWCTEN_SRC(3)
#define S526_GPCT_MODE_CTEN_CTRL(x)     ((x) << 7)
#define S526_GPCT_MODE_CTEN_CTRL_MASK   S526_GPCT_MODE_CTEN_CTRL(0x3)
#define S526_GPCT_MODE_CTEN_CTRL_DIS    S526_GPCT_MODE_CTEN_CTRL(0)
#define S526_GPCT_MODE_CTEN_CTRL_ENA    S526_GPCT_MODE_CTEN_CTRL(1)
#define S526_GPCT_MODE_CTEN_CTRL_HW     S526_GPCT_MODE_CTEN_CTRL(2)
#define S526_GPCT_MODE_CTEN_CTRL_INVHW  S526_GPCT_MODE_CTEN_CTRL(3)
#define S526_GPCT_MODE_CLK_SRC(x)       ((x) << 9)
#define S526_GPCT_MODE_CLK_SRC_MASK     S526_GPCT_MODE_CLK_SRC(0x3)
/* if count direction control set to quadrature */
#define S526_GPCT_MODE_CLK_SRC_QUADX1   S526_GPCT_MODE_CLK_SRC(0)
#define S526_GPCT_MODE_CLK_SRC_QUADX2   S526_GPCT_MODE_CLK_SRC(1)
#define S526_GPCT_MODE_CLK_SRC_QUADX4   S526_GPCT_MODE_CLK_SRC(2)
#define S526_GPCT_MODE_CLK_SRC_QUADX4_  S526_GPCT_MODE_CLK_SRC(3)
/* if count direction control set to software control */
#define S526_GPCT_MODE_CLK_SRC_ARISE    S526_GPCT_MODE_CLK_SRC(0)
#define S526_GPCT_MODE_CLK_SRC_AFALL    S526_GPCT_MODE_CLK_SRC(1)
#define S526_GPCT_MODE_CLK_SRC_INT      S526_GPCT_MODE_CLK_SRC(2)
#define S526_GPCT_MODE_CLK_SRC_INTHALF  S526_GPCT_MODE_CLK_SRC(3)
#define S526_GPCT_MODE_CT_DIR(x)        ((x) << 11)
#define S526_GPCT_MODE_CT_DIR_MASK      S526_GPCT_MODE_CT_DIR(0x1)
/* if count direction control set to software control */
#define S526_GPCT_MODE_CT_DIR_UP        S526_GPCT_MODE_CT_DIR(0)
#define S526_GPCT_MODE_CT_DIR_DOWN      S526_GPCT_MODE_CT_DIR(1)
#define S526_GPCT_MODE_CTDIR_CTRL(x)    ((x) << 12)
#define S526_GPCT_MODE_CTDIR_CTRL_MASK  S526_GPCT_MODE_CTDIR_CTRL(0x1)
#define S526_GPCT_MODE_CTDIR_CTRL_QUAD  S526_GPCT_MODE_CTDIR_CTRL(0)
#define S526_GPCT_MODE_CTDIR_CTRL_SOFT  S526_GPCT_MODE_CTDIR_CTRL(1)
#define S526_GPCT_MODE_LATCH_CTRL(x)    ((x) << 13)
#define S526_GPCT_MODE_LATCH_CTRL_MASK  S526_GPCT_MODE_LATCH_CTRL(0x1)
#define S526_GPCT_MODE_LATCH_CTRL_READ  S526_GPCT_MODE_LATCH_CTRL(0)
#define S526_GPCT_MODE_LATCH_CTRL_EVENT S526_GPCT_MODE_LATCH_CTRL(1)
#define S526_GPCT_MODE_PR_SELECT(x)     ((x) << 14)
#define S526_GPCT_MODE_PR_SELECT_MASK   S526_GPCT_MODE_PR_SELECT(0x1)
#define S526_GPCT_MODE_PR_SELECT_PR0    S526_GPCT_MODE_PR_SELECT(0)
#define S526_GPCT_MODE_PR_SELECT_PR1    S526_GPCT_MODE_PR_SELECT(1)
/* Control/Status - R = readable, W = writeable, C = write 1 to clear */
#define S526_GPCT_CTRL_REG(x)   (0x18 + ((x) * 8))
#define S526_GPCT_CTRL_EV_STATUS(x)     ((x) << 0)              /* RC */
#define S526_GPCT_CTRL_EV_STATUS_MASK   S526_GPCT_EV_STATUS(0xf)
#define S526_GPCT_CTRL_EV_STATUS_NONE   S526_GPCT_EV_STATUS(0)
/* these 4 bits can be OR'ed */
#define S526_GPCT_CTRL_EV_STATUS_ECAP   S526_GPCT_EV_STATUS(0x1)
#define S526_GPCT_CTRL_EV_STATUS_ICAPN  S526_GPCT_EV_STATUS(0x2)
#define S526_GPCT_CTRL_EV_STATUS_ICAPP  S526_GPCT_EV_STATUS(0x4)
#define S526_GPCT_CTRL_EV_STATUS_RCAP   S526_GPCT_EV_STATUS(0x8)
#define S526_GPCT_CTRL_COUT_STATUS      BIT(4)                  /* R */
#define S526_GPCT_CTRL_INDEX_STATUS     BIT(5)                  /* R */
#define S525_GPCT_CTRL_INTEN(x)         ((x) << 6)              /* W */
#define S525_GPCT_CTRL_INTEN_MASK       S526_GPCT_CTRL_INTEN(0xf)
#define S525_GPCT_CTRL_INTEN_NONE       S526_GPCT_CTRL_INTEN(0)
/* these 4 bits can be OR'ed */
#define S525_GPCT_CTRL_INTEN_ERROR      S526_GPCT_CTRL_INTEN(0x1)
#define S525_GPCT_CTRL_INTEN_IXFALL     S526_GPCT_CTRL_INTEN(0x2)
#define S525_GPCT_CTRL_INTEN_IXRISE     S526_GPCT_CTRL_INTEN(0x4)
#define S525_GPCT_CTRL_INTEN_RO         S526_GPCT_CTRL_INTEN(0x8)
#define S525_GPCT_CTRL_LATCH_SEL(x)     ((x) << 10)             /* W */
#define S525_GPCT_CTRL_LATCH_SEL_MASK   S526_GPCT_CTRL_LATCH_SEL(0x7)
#define S525_GPCT_CTRL_LATCH_SEL_NONE   S526_GPCT_CTRL_LATCH_SEL(0)
/* these 3 bits can be OR'ed */
#define S525_GPCT_CTRL_LATCH_SEL_IXFALL S526_GPCT_CTRL_LATCH_SEL(0x1)
#define S525_GPCT_CTRL_LATCH_SEL_IXRISE S526_GPCT_CTRL_LATCH_SEL(0x2)
#define S525_GPCT_CTRL_LATCH_SEL_ITIMER S526_GPCT_CTRL_LATCH_SEL(0x4)
#define S525_GPCT_CTRL_CT_ARM           BIT(13)                 /* W */
#define S525_GPCT_CTRL_CT_LOAD          BIT(14)                 /* W */
#define S526_GPCT_CTRL_CT_RESET         BIT(15)                 /* W */
#define S526_EEPROM_DATA_REG    0x32
#define S526_EEPROM_CTRL_REG    0x34
#define S526_EEPROM_CTRL_ADDR(x) (((x) & 0x3f) << 3)
#define S526_EEPROM_CTRL(x)     (((x) & 0x3) << 1)
#define S526_EEPROM_CTRL_READ   S526_EEPROM_CTRL(2)
#define S526_EEPROM_CTRL_START  BIT(0)

struct s526_private {
        unsigned int gpct_config[4];
        unsigned short ai_ctrl;
};

static void s526_gpct_write(struct comedi_device *dev,
                            unsigned int chan, unsigned int val)
{
        /* write high word then low word */
        outw((val >> 16) & 0xffff, dev->iobase + S526_GPCT_MSB_REG(chan));
        outw(val & 0xffff, dev->iobase + S526_GPCT_LSB_REG(chan));
}

static unsigned int s526_gpct_read(struct comedi_device *dev,
                                   unsigned int chan)
{
        unsigned int val;

        /* read the low word then high word */
        val = inw(dev->iobase + S526_GPCT_LSB_REG(chan)) & 0xffff;
        val |= (inw(dev->iobase + S526_GPCT_MSB_REG(chan)) & 0xff) << 16;

        return val;
}

static int s526_gpct_rinsn(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++)
                data[i] = s526_gpct_read(dev, chan);

        return insn->n;
}

static int s526_gpct_insn_config(struct comedi_device *dev,
                                 struct comedi_subdevice *s,
                                 struct comedi_insn *insn,
                                 unsigned int *data)
{
        struct s526_private *devpriv = dev->private;
        unsigned int chan = CR_CHAN(insn->chanspec);
        unsigned int val;

        /*
         * Check what type of Counter the user requested
         * data[0] contains the Application type
         */
        switch (data[0]) {
        case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
                /*
                 * data[0]: Application Type
                 * data[1]: Counter Mode Register Value
                 * data[2]: Pre-load Register Value
                 * data[3]: Conter Control Register
                 */
                devpriv->gpct_config[chan] = data[0];

#if 1
                /*  Set Counter Mode Register */
                val = data[1] & 0xffff;
                outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));

                /*  Reset the counter if it is software preload */
                if ((val & S526_GPCT_MODE_AUTOLOAD_MASK) ==
                    S526_GPCT_MODE_AUTOLOAD_NONE) {
                        /*  Reset the counter */
                        outw(S526_GPCT_CTRL_CT_RESET,
                             dev->iobase + S526_GPCT_CTRL_REG(chan));
                        /*
                         * Load the counter from PR0
                         * outw(S526_GPCT_CTRL_CT_LOAD,
                         *      dev->iobase + S526_GPCT_CTRL_REG(chan));
                         */
                }
#else
                val = S526_GPCT_MODE_CTDIR_CTRL_QUAD;

                /*  data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
                if (data[1] == GPCT_X2)
                        val |= S526_GPCT_MODE_CLK_SRC_QUADX2;
                else if (data[1] == GPCT_X4)
                        val |= S526_GPCT_MODE_CLK_SRC_QUADX4;
                else
                        val |= S526_GPCT_MODE_CLK_SRC_QUADX1;

                /*  When to take into account the indexpulse: */
                /*
                 * if (data[2] == GPCT_IndexPhaseLowLow) {
                 * } else if (data[2] == GPCT_IndexPhaseLowHigh) {
                 * } else if (data[2] == GPCT_IndexPhaseHighLow) {
                 * } else if (data[2] == GPCT_IndexPhaseHighHigh) {
                 * }
                 */
                /*  Take into account the index pulse? */
                if (data[3] == GPCT_RESET_COUNTER_ON_INDEX) {
                        /*  Auto load with INDEX^ */
                        val |= S526_GPCT_MODE_AUTOLOAD_IXRISE;
                }

                /*  Set Counter Mode Register */
                val = data[1] & 0xffff;
                outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));

                /*  Load the pre-load register */
                s526_gpct_write(dev, chan, data[2]);

                /*  Write the Counter Control Register */
                if (data[3])
                        outw(data[3] & 0xffff,
                             dev->iobase + S526_GPCT_CTRL_REG(chan));

                /*  Reset the counter if it is software preload */
                if ((val & S526_GPCT_MODE_AUTOLOAD_MASK) ==
                    S526_GPCT_MODE_AUTOLOAD_NONE) {
                        /*  Reset the counter */
                        outw(S526_GPCT_CTRL_CT_RESET,
                             dev->iobase + S526_GPCT_CTRL_REG(chan));
                        /*  Load the counter from PR0 */
                        outw(S526_GPCT_CTRL_CT_LOAD,
                             dev->iobase + S526_GPCT_CTRL_REG(chan));
                }
#endif
                break;

        case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
                /*
                 * data[0]: Application Type
                 * data[1]: Counter Mode Register Value
                 * data[2]: Pre-load Register 0 Value
                 * data[3]: Pre-load Register 1 Value
                 * data[4]: Conter Control Register
                 */
                devpriv->gpct_config[chan] = data[0];

                /*  Set Counter Mode Register */
                val = data[1] & 0xffff;
                /* Select PR0 */
                val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
                val |= S526_GPCT_MODE_PR_SELECT_PR0;
                outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));

                /* Load the pre-load register 0 */
                s526_gpct_write(dev, chan, data[2]);

                /*  Set Counter Mode Register */
                val = data[1] & 0xffff;
                /* Select PR1 */
                val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
                val |= S526_GPCT_MODE_PR_SELECT_PR1;
                outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));

                /* Load the pre-load register 1 */
                s526_gpct_write(dev, chan, data[3]);

                /*  Write the Counter Control Register */
                if (data[4]) {
                        val = data[4] & 0xffff;
                        outw(val, dev->iobase + S526_GPCT_CTRL_REG(chan));
                }
                break;

        case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
                /*
                 * data[0]: Application Type
                 * data[1]: Counter Mode Register Value
                 * data[2]: Pre-load Register 0 Value
                 * data[3]: Pre-load Register 1 Value
                 * data[4]: Conter Control Register
                 */
                devpriv->gpct_config[chan] = data[0];

                /*  Set Counter Mode Register */
                val = data[1] & 0xffff;
                /* Select PR0 */
                val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
                val |= S526_GPCT_MODE_PR_SELECT_PR0;
                outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));

                /* Load the pre-load register 0 */
                s526_gpct_write(dev, chan, data[2]);

                /*  Set Counter Mode Register */
                val = data[1] & 0xffff;
                /* Select PR1 */
                val &= ~S526_GPCT_MODE_PR_SELECT_MASK;
                val |= S526_GPCT_MODE_PR_SELECT_PR1;
                outw(val, dev->iobase + S526_GPCT_MODE_REG(chan));

                /* Load the pre-load register 1 */
                s526_gpct_write(dev, chan, data[3]);

                /*  Write the Counter Control Register */
                if (data[4]) {
                        val = data[4] & 0xffff;
                        outw(val, dev->iobase + S526_GPCT_CTRL_REG(chan));
                }
                break;

        default:
                return -EINVAL;
        }

        return insn->n;
}

static int s526_gpct_winsn(struct comedi_device *dev,
                           struct comedi_subdevice *s,
                           struct comedi_insn *insn,
                           unsigned int *data)
{
        struct s526_private *devpriv = dev->private;
        unsigned int chan = CR_CHAN(insn->chanspec);

        inw(dev->iobase + S526_GPCT_MODE_REG(chan));    /* Is this required? */

        /*  Check what Application of Counter this channel is configured for */
        switch (devpriv->gpct_config[chan]) {
        case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
                /*
                 * data[0] contains the PULSE_WIDTH
                 * data[1] contains the PULSE_PERIOD
                 * @pre PULSE_PERIOD > PULSE_WIDTH > 0
                 * The above periods must be expressed as a multiple of the
                 * pulse frequency on the selected source
                 */
                if ((data[1] <= data[0]) || !data[0])
                        return -EINVAL;
                /* to write the PULSE_WIDTH */
                fallthrough;
        case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
        case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
                s526_gpct_write(dev, chan, data[0]);
                break;

        default:
                return -EINVAL;
        }

        return insn->n;
}

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

        status = inw(dev->iobase + S526_INT_STATUS_REG);
        if (status & context) {
                /* we got our eoc event, clear it */
                outw(context, dev->iobase + S526_INT_STATUS_REG);
                return 0;
        }
        return -EBUSY;
}

static int s526_ai_insn_read(struct comedi_device *dev,
                             struct comedi_subdevice *s,
                             struct comedi_insn *insn,
                             unsigned int *data)
{
        struct s526_private *devpriv = dev->private;
        unsigned int chan = CR_CHAN(insn->chanspec);
        unsigned int ctrl;
        unsigned int val;
        int ret;
        int i;

        ctrl = S526_AI_CTRL_CONV(chan) | S526_AI_CTRL_READ(chan) |
               S526_AI_CTRL_START;
        if (ctrl != devpriv->ai_ctrl) {
                /*
                 * The multiplexor needs to change, enable the 15us
                 * delay for the first sample.
                 */
                devpriv->ai_ctrl = ctrl;
                ctrl |= S526_AI_CTRL_DELAY;
        }

        for (i = 0; i < insn->n; i++) {
                /* trigger conversion */
                outw(ctrl, dev->iobase + S526_AI_CTRL_REG);
                ctrl &= ~S526_AI_CTRL_DELAY;

                /* wait for conversion to end */
                ret = comedi_timeout(dev, s, insn, s526_eoc, S526_INT_AI);
                if (ret)
                        return ret;

                val = inw(dev->iobase + S526_AI_REG);
                data[i] = comedi_offset_munge(s, val);
        }

        return insn->n;
}

static int s526_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 ctrl = S526_AO_CTRL_CHAN(chan);
        unsigned int val = s->readback[chan];
        int ret;
        int i;

        outw(ctrl, dev->iobase + S526_AO_CTRL_REG);
        ctrl |= S526_AO_CTRL_START;

        for (i = 0; i < insn->n; i++) {
                val = data[i];
                outw(val, dev->iobase + S526_AO_REG);
                outw(ctrl, dev->iobase + S526_AO_CTRL_REG);

                /* wait for conversion to end */
                ret = comedi_timeout(dev, s, insn, s526_eoc, S526_INT_AO);
                if (ret)
                        return ret;
        }
        s->readback[chan] = val;

        return insn->n;
}

static int s526_dio_insn_bits(struct comedi_device *dev,
                              struct comedi_subdevice *s,
                              struct comedi_insn *insn,
                              unsigned int *data)
{
        if (comedi_dio_update_state(s, data))
                outw(s->state, dev->iobase + S526_DIO_CTRL_REG);

        data[1] = inw(dev->iobase + S526_DIO_CTRL_REG) & 0xff;

        return insn->n;
}

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

        /*
         * Digital I/O can be configured as inputs or outputs in
         * groups of 4; DIO group 1 (DIO0-3) and DIO group 2 (DIO4-7).
         */
        if (chan < 4)
                mask = 0x0f;
        else
                mask = 0xf0;

        ret = comedi_dio_insn_config(dev, s, insn, data, mask);
        if (ret)
                return ret;

        if (s->io_bits & 0x0f)
                s->state |= S526_DIO_CTRL_GRP1_OUT;
        else
                s->state &= ~S526_DIO_CTRL_GRP1_OUT;
        if (s->io_bits & 0xf0)
                s->state |= S526_DIO_CTRL_GRP2_OUT;
        else
                s->state &= ~S526_DIO_CTRL_GRP2_OUT;

        outw(s->state, dev->iobase + S526_DIO_CTRL_REG);

        return insn->n;
}

static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
{
        struct s526_private *devpriv;
        struct comedi_subdevice *s;
        int ret;

        ret = comedi_request_region(dev, it->options[0], 0x40);
        if (ret)
                return ret;

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

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

        /* General-Purpose Counter/Timer (GPCT) */
        s = &dev->subdevices[0];
        s->type         = COMEDI_SUBD_COUNTER;
        s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
        s->n_chan       = 4;
        s->maxdata      = 0x00ffffff;
        s->insn_read    = s526_gpct_rinsn;
        s->insn_config  = s526_gpct_insn_config;
        s->insn_write   = s526_gpct_winsn;

        /*
         * Analog Input subdevice
         * channels 0 to 7 are the regular differential inputs
         * channel 8 is "reference 0" (+10V)
         * channel 9 is "reference 1" (0V)
         */
        s = &dev->subdevices[1];
        s->type         = COMEDI_SUBD_AI;
        s->subdev_flags = SDF_READABLE | SDF_DIFF;
        s->n_chan       = 10;
        s->maxdata      = 0xffff;
        s->range_table  = &range_bipolar10;
        s->len_chanlist = 16;
        s->insn_read    = s526_ai_insn_read;

        /* Analog Output subdevice */
        s = &dev->subdevices[2];
        s->type         = COMEDI_SUBD_AO;
        s->subdev_flags = SDF_WRITABLE;
        s->n_chan       = 4;
        s->maxdata      = 0xffff;
        s->range_table  = &range_bipolar10;
        s->insn_write   = s526_ao_insn_write;

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

        /* Digital I/O subdevice */
        s = &dev->subdevices[3];
        s->type         = COMEDI_SUBD_DIO;
        s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
        s->n_chan       = 8;
        s->maxdata      = 1;
        s->range_table  = &range_digital;
        s->insn_bits    = s526_dio_insn_bits;
        s->insn_config  = s526_dio_insn_config;

        return 0;
}

static struct comedi_driver s526_driver = {
        .driver_name    = "s526",
        .module         = THIS_MODULE,
        .attach         = s526_attach,
        .detach         = comedi_legacy_detach,
};
module_comedi_driver(s526_driver);

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