Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / staging / comedi / drivers / ii_pci20kc.c
diff --git a/kernel/drivers/staging/comedi/drivers/ii_pci20kc.c b/kernel/drivers/staging/comedi/drivers/ii_pci20kc.c
new file mode 100644 (file)
index 0000000..0768bc4
--- /dev/null
@@ -0,0 +1,526 @@
+/*
+ * ii_pci20kc.c
+ * Driver for Intelligent Instruments PCI-20001C carrier board and modules.
+ *
+ * Copyright (C) 2000 Markus Kempf <kempf@matsci.uni-sb.de>
+ * with suggestions from David Schleef         16.06.2000
+ */
+
+/*
+ * Driver: ii_pci20kc
+ * Description: Intelligent Instruments PCI-20001C carrier board
+ * Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc)
+ * Author: Markus Kempf <kempf@matsci.uni-sb.de>
+ * Status: works
+ *
+ * Supports the PCI-20001C-1a and PCI-20001C-2a carrier boards. The
+ * -2a version has 32 on-board DIO channels. Three add-on modules
+ * can be added to the carrier board for additional functionality.
+ *
+ * Supported add-on modules:
+ *     PCI-20006M-1   1 channel, 16-bit analog output module
+ *     PCI-20006M-2   2 channel, 16-bit analog output module
+ *     PCI-20341M-1A  4 channel, 16-bit analog input module
+ *
+ * Options:
+ *   0   Board base address
+ *   1   IRQ (not-used)
+ */
+
+#include <linux/module.h>
+#include "../comedidev.h"
+
+/*
+ * Register I/O map
+ */
+#define II20K_SIZE                     0x400
+#define II20K_MOD_OFFSET               0x100
+#define II20K_ID_REG                   0x00
+#define II20K_ID_MOD1_EMPTY            (1 << 7)
+#define II20K_ID_MOD2_EMPTY            (1 << 6)
+#define II20K_ID_MOD3_EMPTY            (1 << 5)
+#define II20K_ID_MASK                  0x1f
+#define II20K_ID_PCI20001C_1A          0x1b    /* no on-board DIO */
+#define II20K_ID_PCI20001C_2A          0x1d    /* on-board DIO */
+#define II20K_MOD_STATUS_REG           0x40
+#define II20K_MOD_STATUS_IRQ_MOD1      (1 << 7)
+#define II20K_MOD_STATUS_IRQ_MOD2      (1 << 6)
+#define II20K_MOD_STATUS_IRQ_MOD3      (1 << 5)
+#define II20K_DIO0_REG                 0x80
+#define II20K_DIO1_REG                 0x81
+#define II20K_DIR_ENA_REG              0x82
+#define II20K_DIR_DIO3_OUT             (1 << 7)
+#define II20K_DIR_DIO2_OUT             (1 << 6)
+#define II20K_BUF_DISAB_DIO3           (1 << 5)
+#define II20K_BUF_DISAB_DIO2           (1 << 4)
+#define II20K_DIR_DIO1_OUT             (1 << 3)
+#define II20K_DIR_DIO0_OUT             (1 << 2)
+#define II20K_BUF_DISAB_DIO1           (1 << 1)
+#define II20K_BUF_DISAB_DIO0           (1 << 0)
+#define II20K_CTRL01_REG               0x83
+#define II20K_CTRL01_SET               (1 << 7)
+#define II20K_CTRL01_DIO0_IN           (1 << 4)
+#define II20K_CTRL01_DIO1_IN           (1 << 1)
+#define II20K_DIO2_REG                 0xc0
+#define II20K_DIO3_REG                 0xc1
+#define II20K_CTRL23_REG               0xc3
+#define II20K_CTRL23_SET               (1 << 7)
+#define II20K_CTRL23_DIO2_IN           (1 << 4)
+#define II20K_CTRL23_DIO3_IN           (1 << 1)
+
+#define II20K_ID_PCI20006M_1           0xe2    /* 1 AO channels */
+#define II20K_ID_PCI20006M_2           0xe3    /* 2 AO channels */
+#define II20K_AO_STRB_REG(x)           (0x0b + ((x) * 0x08))
+#define II20K_AO_LSB_REG(x)            (0x0d + ((x) * 0x08))
+#define II20K_AO_MSB_REG(x)            (0x0e + ((x) * 0x08))
+#define II20K_AO_STRB_BOTH_REG         0x1b
+
+#define II20K_ID_PCI20341M_1           0x77    /* 4 AI channels */
+#define II20K_AI_STATUS_CMD_REG                0x01
+#define II20K_AI_STATUS_CMD_BUSY       (1 << 7)
+#define II20K_AI_STATUS_CMD_HW_ENA     (1 << 1)
+#define II20K_AI_STATUS_CMD_EXT_START  (1 << 0)
+#define II20K_AI_LSB_REG               0x02
+#define II20K_AI_MSB_REG               0x03
+#define II20K_AI_PACER_RESET_REG       0x04
+#define II20K_AI_16BIT_DATA_REG                0x06
+#define II20K_AI_CONF_REG              0x10
+#define II20K_AI_CONF_ENA              (1 << 2)
+#define II20K_AI_OPT_REG               0x11
+#define II20K_AI_OPT_TRIG_ENA          (1 << 5)
+#define II20K_AI_OPT_TRIG_INV          (1 << 4)
+#define II20K_AI_OPT_TIMEBASE(x)       (((x) & 0x3) << 1)
+#define II20K_AI_OPT_BURST_MODE                (1 << 0)
+#define II20K_AI_STATUS_REG            0x12
+#define II20K_AI_STATUS_INT            (1 << 7)
+#define II20K_AI_STATUS_TRIG           (1 << 6)
+#define II20K_AI_STATUS_TRIG_ENA       (1 << 5)
+#define II20K_AI_STATUS_PACER_ERR      (1 << 2)
+#define II20K_AI_STATUS_DATA_ERR       (1 << 1)
+#define II20K_AI_STATUS_SET_TIME_ERR   (1 << 0)
+#define II20K_AI_LAST_CHAN_ADDR_REG    0x13
+#define II20K_AI_CUR_ADDR_REG          0x14
+#define II20K_AI_SET_TIME_REG          0x15
+#define II20K_AI_DELAY_LSB_REG         0x16
+#define II20K_AI_DELAY_MSB_REG         0x17
+#define II20K_AI_CHAN_ADV_REG          0x18
+#define II20K_AI_CHAN_RESET_REG                0x19
+#define II20K_AI_START_TRIG_REG                0x1a
+#define II20K_AI_COUNT_RESET_REG       0x1b
+#define II20K_AI_CHANLIST_REG          0x80
+#define II20K_AI_CHANLIST_ONBOARD_ONLY (1 << 5)
+#define II20K_AI_CHANLIST_GAIN(x)      (((x) & 0x3) << 3)
+#define II20K_AI_CHANLIST_MUX_ENA      (1 << 2)
+#define II20K_AI_CHANLIST_CHAN(x)      (((x) & 0x3) << 0)
+#define II20K_AI_CHANLIST_LEN          0x80
+
+/* the AO range is set by jumpers on the 20006M module */
+static const struct comedi_lrange ii20k_ao_ranges = {
+       3, {
+               BIP_RANGE(5),   /* Chan 0 - W1/W3 in   Chan 1 - W2/W4 in  */
+               UNI_RANGE(10),  /* Chan 0 - W1/W3 out  Chan 1 - W2/W4 in  */
+               BIP_RANGE(10)   /* Chan 0 - W1/W3 in   Chan 1 - W2/W4 out */
+       }
+};
+
+static const struct comedi_lrange ii20k_ai_ranges = {
+       4, {
+               BIP_RANGE(5),           /* gain 1 */
+               BIP_RANGE(0.5),         /* gain 10 */
+               BIP_RANGE(0.05),        /* gain 100 */
+               BIP_RANGE(0.025)        /* gain 200 */
+       },
+};
+
+static void __iomem *ii20k_module_iobase(struct comedi_device *dev,
+                                        struct comedi_subdevice *s)
+{
+       return dev->mmio + (s->index + 1) * II20K_MOD_OFFSET;
+}
+
+static int ii20k_ao_insn_write(struct comedi_device *dev,
+                              struct comedi_subdevice *s,
+                              struct comedi_insn *insn,
+                              unsigned int *data)
+{
+       void __iomem *iobase = ii20k_module_iobase(dev, s);
+       unsigned int chan = CR_CHAN(insn->chanspec);
+       int i;
+
+       for (i = 0; i < insn->n; i++) {
+               unsigned int val = data[i];
+
+               s->readback[chan] = val;
+
+               /* munge data */
+               val += ((s->maxdata + 1) >> 1);
+               val &= s->maxdata;
+
+               writeb(val & 0xff, iobase + II20K_AO_LSB_REG(chan));
+               writeb((val >> 8) & 0xff, iobase + II20K_AO_MSB_REG(chan));
+               writeb(0x00, iobase + II20K_AO_STRB_REG(chan));
+       }
+
+       return insn->n;
+}
+
+static int ii20k_ai_eoc(struct comedi_device *dev,
+                       struct comedi_subdevice *s,
+                       struct comedi_insn *insn,
+                       unsigned long context)
+{
+       void __iomem *iobase = ii20k_module_iobase(dev, s);
+       unsigned char status;
+
+       status = readb(iobase + II20K_AI_STATUS_REG);
+       if ((status & II20K_AI_STATUS_INT) == 0)
+               return 0;
+       return -EBUSY;
+}
+
+static void ii20k_ai_setup(struct comedi_device *dev,
+                          struct comedi_subdevice *s,
+                          unsigned int chanspec)
+{
+       void __iomem *iobase = ii20k_module_iobase(dev, s);
+       unsigned int chan = CR_CHAN(chanspec);
+       unsigned int range = CR_RANGE(chanspec);
+       unsigned char val;
+
+       /* initialize module */
+       writeb(II20K_AI_CONF_ENA, iobase + II20K_AI_CONF_REG);
+
+       /* software conversion */
+       writeb(0, iobase + II20K_AI_STATUS_CMD_REG);
+
+       /* set the time base for the settling time counter based on the gain */
+       val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2);
+       writeb(val, iobase + II20K_AI_OPT_REG);
+
+       /* set the settling time counter based on the gain */
+       val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99;
+       writeb(val, iobase + II20K_AI_SET_TIME_REG);
+
+       /* set number of input channels */
+       writeb(1, iobase + II20K_AI_LAST_CHAN_ADDR_REG);
+
+       /* set the channel list byte */
+       val = II20K_AI_CHANLIST_ONBOARD_ONLY |
+             II20K_AI_CHANLIST_MUX_ENA |
+             II20K_AI_CHANLIST_GAIN(range) |
+             II20K_AI_CHANLIST_CHAN(chan);
+       writeb(val, iobase + II20K_AI_CHANLIST_REG);
+
+       /* reset settling time counter and trigger delay counter */
+       writeb(0, iobase + II20K_AI_COUNT_RESET_REG);
+
+       /* reset channel scanner */
+       writeb(0, iobase + II20K_AI_CHAN_RESET_REG);
+}
+
+static int ii20k_ai_insn_read(struct comedi_device *dev,
+                             struct comedi_subdevice *s,
+                             struct comedi_insn *insn,
+                             unsigned int *data)
+{
+       void __iomem *iobase = ii20k_module_iobase(dev, s);
+       int ret;
+       int i;
+
+       ii20k_ai_setup(dev, s, insn->chanspec);
+
+       for (i = 0; i < insn->n; i++) {
+               unsigned int val;
+
+               /* generate a software start convert signal */
+               readb(iobase + II20K_AI_PACER_RESET_REG);
+
+               ret = comedi_timeout(dev, s, insn, ii20k_ai_eoc, 0);
+               if (ret)
+                       return ret;
+
+               val = readb(iobase + II20K_AI_LSB_REG);
+               val |= (readb(iobase + II20K_AI_MSB_REG) << 8);
+
+               /* munge two's complement data */
+               val += ((s->maxdata + 1) >> 1);
+               val &= s->maxdata;
+
+               data[i] = val;
+       }
+
+       return insn->n;
+}
+
+static void ii20k_dio_config(struct comedi_device *dev,
+                            struct comedi_subdevice *s)
+{
+       unsigned char ctrl01 = 0;
+       unsigned char ctrl23 = 0;
+       unsigned char dir_ena = 0;
+
+       /* port 0 - channels 0-7 */
+       if (s->io_bits & 0x000000ff) {
+               /* output port */
+               ctrl01 &= ~II20K_CTRL01_DIO0_IN;
+               dir_ena &= ~II20K_BUF_DISAB_DIO0;
+               dir_ena |= II20K_DIR_DIO0_OUT;
+       } else {
+               /* input port */
+               ctrl01 |= II20K_CTRL01_DIO0_IN;
+               dir_ena &= ~II20K_DIR_DIO0_OUT;
+       }
+
+       /* port 1 - channels 8-15 */
+       if (s->io_bits & 0x0000ff00) {
+               /* output port */
+               ctrl01 &= ~II20K_CTRL01_DIO1_IN;
+               dir_ena &= ~II20K_BUF_DISAB_DIO1;
+               dir_ena |= II20K_DIR_DIO1_OUT;
+       } else {
+               /* input port */
+               ctrl01 |= II20K_CTRL01_DIO1_IN;
+               dir_ena &= ~II20K_DIR_DIO1_OUT;
+       }
+
+       /* port 2 - channels 16-23 */
+       if (s->io_bits & 0x00ff0000) {
+               /* output port */
+               ctrl23 &= ~II20K_CTRL23_DIO2_IN;
+               dir_ena &= ~II20K_BUF_DISAB_DIO2;
+               dir_ena |= II20K_DIR_DIO2_OUT;
+       } else {
+               /* input port */
+               ctrl23 |= II20K_CTRL23_DIO2_IN;
+               dir_ena &= ~II20K_DIR_DIO2_OUT;
+       }
+
+       /* port 3 - channels 24-31 */
+       if (s->io_bits & 0xff000000) {
+               /* output port */
+               ctrl23 &= ~II20K_CTRL23_DIO3_IN;
+               dir_ena &= ~II20K_BUF_DISAB_DIO3;
+               dir_ena |= II20K_DIR_DIO3_OUT;
+       } else {
+               /* input port */
+               ctrl23 |= II20K_CTRL23_DIO3_IN;
+               dir_ena &= ~II20K_DIR_DIO3_OUT;
+       }
+
+       ctrl23 |= II20K_CTRL01_SET;
+       ctrl23 |= II20K_CTRL23_SET;
+
+       /* order is important */
+       writeb(ctrl01, dev->mmio + II20K_CTRL01_REG);
+       writeb(ctrl23, dev->mmio + II20K_CTRL23_REG);
+       writeb(dir_ena, dev->mmio + II20K_DIR_ENA_REG);
+}
+
+static int ii20k_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;
+
+       if (chan < 8)
+               mask = 0x000000ff;
+       else if (chan < 16)
+               mask = 0x0000ff00;
+       else if (chan < 24)
+               mask = 0x00ff0000;
+       else
+               mask = 0xff000000;
+
+       ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+       if (ret)
+               return ret;
+
+       ii20k_dio_config(dev, s);
+
+       return insn->n;
+}
+
+static int ii20k_dio_insn_bits(struct comedi_device *dev,
+                              struct comedi_subdevice *s,
+                              struct comedi_insn *insn,
+                              unsigned int *data)
+{
+       unsigned int mask;
+
+       mask = comedi_dio_update_state(s, data);
+       if (mask) {
+               if (mask & 0x000000ff)
+                       writeb((s->state >> 0) & 0xff,
+                              dev->mmio + II20K_DIO0_REG);
+               if (mask & 0x0000ff00)
+                       writeb((s->state >> 8) & 0xff,
+                              dev->mmio + II20K_DIO1_REG);
+               if (mask & 0x00ff0000)
+                       writeb((s->state >> 16) & 0xff,
+                              dev->mmio + II20K_DIO2_REG);
+               if (mask & 0xff000000)
+                       writeb((s->state >> 24) & 0xff,
+                              dev->mmio + II20K_DIO3_REG);
+       }
+
+       data[1] = readb(dev->mmio + II20K_DIO0_REG);
+       data[1] |= readb(dev->mmio + II20K_DIO1_REG) << 8;
+       data[1] |= readb(dev->mmio + II20K_DIO2_REG) << 16;
+       data[1] |= readb(dev->mmio + II20K_DIO3_REG) << 24;
+
+       return insn->n;
+}
+
+static int ii20k_init_module(struct comedi_device *dev,
+                            struct comedi_subdevice *s)
+{
+       void __iomem *iobase = ii20k_module_iobase(dev, s);
+       unsigned char id;
+       int ret;
+
+       id = readb(iobase + II20K_ID_REG);
+       switch (id) {
+       case II20K_ID_PCI20006M_1:
+       case II20K_ID_PCI20006M_2:
+               /* Analog Output subdevice */
+               s->type         = COMEDI_SUBD_AO;
+               s->subdev_flags = SDF_WRITABLE;
+               s->n_chan       = (id == II20K_ID_PCI20006M_2) ? 2 : 1;
+               s->maxdata      = 0xffff;
+               s->range_table  = &ii20k_ao_ranges;
+               s->insn_write   = ii20k_ao_insn_write;
+
+               ret = comedi_alloc_subdev_readback(s);
+               if (ret)
+                       return ret;
+               break;
+       case II20K_ID_PCI20341M_1:
+               /* Analog Input subdevice */
+               s->type         = COMEDI_SUBD_AI;
+               s->subdev_flags = SDF_READABLE | SDF_DIFF;
+               s->n_chan       = 4;
+               s->maxdata      = 0xffff;
+               s->range_table  = &ii20k_ai_ranges;
+               s->insn_read    = ii20k_ai_insn_read;
+               break;
+       default:
+               s->type = COMEDI_SUBD_UNUSED;
+               break;
+       }
+
+       return 0;
+}
+
+static int ii20k_attach(struct comedi_device *dev,
+                       struct comedi_devconfig *it)
+{
+       struct comedi_subdevice *s;
+       unsigned int membase;
+       unsigned char id;
+       bool has_dio;
+       int ret;
+
+       membase = it->options[0];
+       if (!membase || (membase & ~(0x100000 - II20K_SIZE))) {
+               dev_warn(dev->class_dev,
+                        "%s: invalid memory address specified\n",
+                        dev->board_name);
+               return -EINVAL;
+       }
+
+       if (!request_mem_region(membase, II20K_SIZE, dev->board_name)) {
+               dev_warn(dev->class_dev, "%s: I/O mem conflict (%#x,%u)\n",
+                        dev->board_name, membase, II20K_SIZE);
+               return -EIO;
+       }
+       dev->iobase = membase;  /* actually, a memory address */
+
+       dev->mmio = ioremap(membase, II20K_SIZE);
+       if (!dev->mmio)
+               return -ENOMEM;
+
+       id = readb(dev->mmio + II20K_ID_REG);
+       switch (id & II20K_ID_MASK) {
+       case II20K_ID_PCI20001C_1A:
+               has_dio = false;
+               break;
+       case II20K_ID_PCI20001C_2A:
+               has_dio = true;
+               break;
+       default:
+               return -ENODEV;
+       }
+
+       ret = comedi_alloc_subdevices(dev, 4);
+       if (ret)
+               return ret;
+
+       s = &dev->subdevices[0];
+       if (id & II20K_ID_MOD1_EMPTY) {
+               s->type = COMEDI_SUBD_UNUSED;
+       } else {
+               ret = ii20k_init_module(dev, s);
+               if (ret)
+                       return ret;
+       }
+
+       s = &dev->subdevices[1];
+       if (id & II20K_ID_MOD2_EMPTY) {
+               s->type = COMEDI_SUBD_UNUSED;
+       } else {
+               ret = ii20k_init_module(dev, s);
+               if (ret)
+                       return ret;
+       }
+
+       s = &dev->subdevices[2];
+       if (id & II20K_ID_MOD3_EMPTY) {
+               s->type = COMEDI_SUBD_UNUSED;
+       } else {
+               ret = ii20k_init_module(dev, s);
+               if (ret)
+                       return ret;
+       }
+
+       /* Digital I/O subdevice */
+       s = &dev->subdevices[3];
+       if (has_dio) {
+               s->type         = COMEDI_SUBD_DIO;
+               s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
+               s->n_chan       = 32;
+               s->maxdata      = 1;
+               s->range_table  = &range_digital;
+               s->insn_bits    = ii20k_dio_insn_bits;
+               s->insn_config  = ii20k_dio_insn_config;
+
+               /* default all channels to input */
+               ii20k_dio_config(dev, s);
+       } else {
+               s->type = COMEDI_SUBD_UNUSED;
+       }
+
+       return 0;
+}
+
+static void ii20k_detach(struct comedi_device *dev)
+{
+       if (dev->mmio)
+               iounmap(dev->mmio);
+       if (dev->iobase)        /* actually, a memory address */
+               release_mem_region(dev->iobase, II20K_SIZE);
+}
+
+static struct comedi_driver ii20k_driver = {
+       .driver_name    = "ii_pci20kc",
+       .module         = THIS_MODULE,
+       .attach         = ii20k_attach,
+       .detach         = ii20k_detach,
+};
+module_comedi_driver(ii20k_driver);
+
+MODULE_AUTHOR("Comedi http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");