Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / drivers / staging / comedi / drivers / s526.c
diff --git a/kernel/drivers/staging/comedi/drivers/s526.c b/kernel/drivers/staging/comedi/drivers/s526.c
new file mode 100644 (file)
index 0000000..6f3e8a0
--- /dev/null
@@ -0,0 +1,614 @@
+/*
+    comedi/drivers/s526.c
+    Sensoray s526 Comedi driver
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+*/
+/*
+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:
+
+comedi_config /dev/comedi0 s526 0x2C0,0x3
+
+*/
+
+#include <linux/module.h>
+#include "../comedidev.h"
+#include <asm/byteorder.h>
+
+#define S526_START_AI_CONV     0
+#define S526_AI_READ           0
+
+/* Ports */
+#define S526_NUM_PORTS 27
+
+/* registers */
+#define REG_TCR 0x00
+#define REG_WDC 0x02
+#define REG_DAC 0x04
+#define REG_ADC 0x06
+#define REG_ADD 0x08
+#define REG_DIO 0x0A
+#define REG_IER 0x0C
+#define REG_ISR 0x0E
+#define REG_MSC 0x10
+#define REG_C0L 0x12
+#define REG_C0H 0x14
+#define REG_C0M 0x16
+#define REG_C0C 0x18
+#define REG_C1L 0x1A
+#define REG_C1H 0x1C
+#define REG_C1M 0x1E
+#define REG_C1C 0x20
+#define REG_C2L 0x22
+#define REG_C2H 0x24
+#define REG_C2M 0x26
+#define REG_C2C 0x28
+#define REG_C3L 0x2A
+#define REG_C3H 0x2C
+#define REG_C3M 0x2E
+#define REG_C3C 0x30
+#define REG_EED 0x32
+#define REG_EEC 0x34
+
+struct counter_mode_register_t {
+#if defined(__LITTLE_ENDIAN_BITFIELD)
+       unsigned short coutSource:1;
+       unsigned short coutPolarity:1;
+       unsigned short autoLoadResetRcap:3;
+       unsigned short hwCtEnableSource:2;
+       unsigned short ctEnableCtrl:2;
+       unsigned short clockSource:2;
+       unsigned short countDir:1;
+       unsigned short countDirCtrl:1;
+       unsigned short outputRegLatchCtrl:1;
+       unsigned short preloadRegSel:1;
+       unsigned short reserved:1;
+ #elif defined(__BIG_ENDIAN_BITFIELD)
+       unsigned short reserved:1;
+       unsigned short preloadRegSel:1;
+       unsigned short outputRegLatchCtrl:1;
+       unsigned short countDirCtrl:1;
+       unsigned short countDir:1;
+       unsigned short clockSource:2;
+       unsigned short ctEnableCtrl:2;
+       unsigned short hwCtEnableSource:2;
+       unsigned short autoLoadResetRcap:3;
+       unsigned short coutPolarity:1;
+       unsigned short coutSource:1;
+#else
+#error Unknown bit field order
+#endif
+};
+
+union cmReg {
+       struct counter_mode_register_t reg;
+       unsigned short value;
+};
+
+struct s526_private {
+       unsigned int gpct_config[4];
+       unsigned short ai_config;
+};
+
+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);
+       unsigned long chan_iobase = dev->iobase + chan * 8;
+       unsigned int lo;
+       unsigned int hi;
+       int i;
+
+       for (i = 0; i < insn->n; i++) {
+               /* Read the low word first */
+               lo = inw(chan_iobase + REG_C0L) & 0xffff;
+               hi = inw(chan_iobase + REG_C0H) & 0xff;
+
+               data[i] = (hi << 16) | lo;
+       }
+
+       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 long chan_iobase = dev->iobase + chan * 8;
+       unsigned int val;
+       union cmReg cmReg;
+
+       /*  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 0
+               /*  Example of Counter Application */
+               /* One-shot (software trigger) */
+               cmReg.reg.coutSource = 0;       /*  out RCAP */
+               cmReg.reg.coutPolarity = 1;     /*  Polarity inverted */
+               cmReg.reg.autoLoadResetRcap = 0;/*  Auto load disabled */
+               cmReg.reg.hwCtEnableSource = 3; /*  NOT RCAP */
+               cmReg.reg.ctEnableCtrl = 2;     /*  Hardware */
+               cmReg.reg.clockSource = 2;      /*  Internal */
+               cmReg.reg.countDir = 1; /*  Down */
+               cmReg.reg.countDirCtrl = 1;     /*  Software */
+               cmReg.reg.outputRegLatchCtrl = 0;       /*  latch on read */
+               cmReg.reg.preloadRegSel = 0;    /*  PR0 */
+               cmReg.reg.reserved = 0;
+
+               outw(cmReg.value, chan_iobase + REG_C0M);
+
+               outw(0x0001, chan_iobase + REG_C0H);
+               outw(0x3C68, chan_iobase + REG_C0L);
+
+               /*  Reset the counter */
+               outw(0x8000, chan_iobase + REG_C0C);
+               /*  Load the counter from PR0 */
+               outw(0x4000, chan_iobase + REG_C0C);
+
+               /*  Reset RCAP (fires one-shot) */
+               outw(0x0008, chan_iobase + REG_C0C);
+
+#endif
+
+#if 1
+               /*  Set Counter Mode Register */
+               cmReg.value = data[1] & 0xffff;
+               outw(cmReg.value, chan_iobase + REG_C0M);
+
+               /*  Reset the counter if it is software preload */
+               if (cmReg.reg.autoLoadResetRcap == 0) {
+                       /*  Reset the counter */
+                       outw(0x8000, chan_iobase + REG_C0C);
+                       /* Load the counter from PR0
+                        * outw(0x4000, chan_iobase + REG_C0C);
+                        */
+               }
+#else
+               /*  0 quadrature, 1 software control */
+               cmReg.reg.countDirCtrl = 0;
+
+               /*  data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
+               if (data[1] == GPCT_X2)
+                       cmReg.reg.clockSource = 1;
+               else if (data[1] == GPCT_X4)
+                       cmReg.reg.clockSource = 2;
+               else
+                       cmReg.reg.clockSource = 0;
+
+               /*  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^ */
+                       cmReg.reg.autoLoadResetRcap = 4;
+
+               /*  Set Counter Mode Register */
+               cmReg.value = data[1] & 0xffff;
+               outw(cmReg.value, chan_iobase + REG_C0M);
+
+               /*  Load the pre-load register high word */
+               val = (data[2] >> 16) & 0xffff;
+               outw(val, chan_iobase + REG_C0H);
+
+               /*  Load the pre-load register low word */
+               val = data[2] & 0xffff;
+               outw(val, chan_iobase + REG_C0L);
+
+               /*  Write the Counter Control Register */
+               if (data[3]) {
+                       val = data[3] & 0xffff;
+                       outw(val, chan_iobase + REG_C0C);
+               }
+               /*  Reset the counter if it is software preload */
+               if (cmReg.reg.autoLoadResetRcap == 0) {
+                       /*  Reset the counter */
+                       outw(0x8000, chan_iobase + REG_C0C);
+                       /*  Load the counter from PR0 */
+                       outw(0x4000, chan_iobase + REG_C0C);
+               }
+#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 */
+               cmReg.value = data[1] & 0xffff;
+               cmReg.reg.preloadRegSel = 0;    /*  PR0 */
+               outw(cmReg.value, chan_iobase + REG_C0M);
+
+               /*  Load the pre-load register 0 high word */
+               val = (data[2] >> 16) & 0xffff;
+               outw(val, chan_iobase + REG_C0H);
+
+               /*  Load the pre-load register 0 low word */
+               val = data[2] & 0xffff;
+               outw(val, chan_iobase + REG_C0L);
+
+               /*  Set Counter Mode Register */
+               cmReg.value = data[1] & 0xffff;
+               cmReg.reg.preloadRegSel = 1;    /*  PR1 */
+               outw(cmReg.value, chan_iobase + REG_C0M);
+
+               /*  Load the pre-load register 1 high word */
+               val = (data[3] >> 16) & 0xffff;
+               outw(val, chan_iobase + REG_C0H);
+
+               /*  Load the pre-load register 1 low word */
+               val = data[3] & 0xffff;
+               outw(val, chan_iobase + REG_C0L);
+
+               /*  Write the Counter Control Register */
+               if (data[4]) {
+                       val = data[4] & 0xffff;
+                       outw(val, chan_iobase + REG_C0C);
+               }
+               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 */
+               cmReg.value = data[1] & 0xffff;
+               cmReg.reg.preloadRegSel = 0;    /*  PR0 */
+               outw(cmReg.value, chan_iobase + REG_C0M);
+
+               /*  Load the pre-load register 0 high word */
+               val = (data[2] >> 16) & 0xffff;
+               outw(val, chan_iobase + REG_C0H);
+
+               /*  Load the pre-load register 0 low word */
+               val = data[2] & 0xffff;
+               outw(val, chan_iobase + REG_C0L);
+
+               /*  Set Counter Mode Register */
+               cmReg.value = data[1] & 0xffff;
+               cmReg.reg.preloadRegSel = 1;    /*  PR1 */
+               outw(cmReg.value, chan_iobase + REG_C0M);
+
+               /*  Load the pre-load register 1 high word */
+               val = (data[3] >> 16) & 0xffff;
+               outw(val, chan_iobase + REG_C0H);
+
+               /*  Load the pre-load register 1 low word */
+               val = data[3] & 0xffff;
+               outw(val, chan_iobase + REG_C0L);
+
+               /*  Write the Counter Control Register */
+               if (data[4]) {
+                       val = data[4] & 0xffff;
+                       outw(val, chan_iobase + REG_C0C);
+               }
+               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);
+       unsigned long chan_iobase = dev->iobase + chan * 8;
+
+       inw(chan_iobase + REG_C0M);     /* Is this read 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;
+
+               /* Fall thru to write the PULSE_WIDTH */
+
+       case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
+       case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
+               outw((data[0] >> 16) & 0xffff, chan_iobase + REG_C0H);
+               outw(data[0] & 0xffff, chan_iobase + REG_C0L);
+               break;
+
+       default:
+               return -EINVAL;
+       }
+
+       return insn->n;
+}
+
+#define ISR_ADC_DONE 0x4
+static int s526_ai_insn_config(struct comedi_device *dev,
+                              struct comedi_subdevice *s,
+                              struct comedi_insn *insn, unsigned int *data)
+{
+       struct s526_private *devpriv = dev->private;
+       int result = -EINVAL;
+
+       if (insn->n < 1)
+               return result;
+
+       result = insn->n;
+
+       /* data[0] : channels was set in relevant bits.
+          data[1] : delay
+        */
+       /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to
+        * enable channels here.  The channel should be enabled in the
+        * INSN_READ handler. */
+
+       /*  Enable ADC interrupt */
+       outw(ISR_ADC_DONE, dev->iobase + REG_IER);
+       devpriv->ai_config = (data[0] & 0x3ff) << 5;
+       if (data[1] > 0)
+               devpriv->ai_config |= 0x8000;   /* set the delay */
+
+       devpriv->ai_config |= 0x0001;           /* ADC start bit */
+
+       return result;
+}
+
+static int s526_ai_eoc(struct comedi_device *dev,
+                      struct comedi_subdevice *s,
+                      struct comedi_insn *insn,
+                      unsigned long context)
+{
+       unsigned int status;
+
+       status = inw(dev->iobase + REG_ISR);
+       if (status & ISR_ADC_DONE)
+               return 0;
+       return -EBUSY;
+}
+
+static int s526_ai_rinsn(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);
+       int n;
+       unsigned short value;
+       unsigned int d;
+       int ret;
+
+       /* Set configured delay, enable channel for this channel only,
+        * select "ADC read" channel, set "ADC start" bit. */
+       value = (devpriv->ai_config & 0x8000) |
+               ((1 << 5) << chan) | (chan << 1) | 0x0001;
+
+       /* convert n samples */
+       for (n = 0; n < insn->n; n++) {
+               /* trigger conversion */
+               outw(value, dev->iobase + REG_ADC);
+
+               /* wait for conversion to end */
+               ret = comedi_timeout(dev, s, insn, s526_ai_eoc, 0);
+               if (ret)
+                       return ret;
+
+               outw(ISR_ADC_DONE, dev->iobase + REG_ISR);
+
+               /* read data */
+               d = inw(dev->iobase + REG_ADD);
+
+               /* munge data */
+               data[n] = d ^ 0x8000;
+       }
+
+       /* return the number of samples read/written */
+       return 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 val = s->readback[chan];
+       int i;
+
+       outw(chan << 1, dev->iobase + REG_DAC);
+
+       for (i = 0; i < insn->n; i++) {
+               val = data[i];
+               outw(val, dev->iobase + REG_ADD);
+               /* starts the D/A conversion */
+               outw((chan << 1) | 1, dev->iobase + REG_DAC);
+       }
+       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 + REG_DIO);
+
+       data[1] = inw(dev->iobase + REG_DIO) & 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;
+
+       if (chan < 4)
+               mask = 0x0f;
+       else
+               mask = 0xf0;
+
+       ret = comedi_dio_insn_config(dev, s, insn, data, mask);
+       if (ret)
+               return ret;
+
+       /* bit 10/11 set the group 1/2's mode */
+       if (s->io_bits & 0x0f)
+               s->state |= (1 << 10);
+       else
+               s->state &= ~(1 << 10);
+       if (s->io_bits & 0xf0)
+               s->state |= (1 << 11);
+       else
+               s->state &= ~(1 << 11);
+
+       outw(s->state, dev->iobase + REG_DIO);
+
+       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;
+
+       s = &dev->subdevices[0];
+       /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
+       s->type = COMEDI_SUBD_COUNTER;
+       s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
+       s->n_chan = 4;
+       s->maxdata = 0x00ffffff;        /* 24 bit counter */
+       s->insn_read = s526_gpct_rinsn;
+       s->insn_config = s526_gpct_insn_config;
+       s->insn_write = s526_gpct_winsn;
+
+       s = &dev->subdevices[1];
+       /* analog input subdevice */
+       s->type = COMEDI_SUBD_AI;
+       s->subdev_flags = SDF_READABLE | SDF_DIFF;
+       /* channels 0 to 7 are the regular differential inputs */
+       /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */
+       s->n_chan = 10;
+       s->maxdata = 0xffff;
+       s->range_table = &range_bipolar10;
+       s->len_chanlist = 16;
+       s->insn_read = s526_ai_rinsn;
+       s->insn_config = s526_ai_insn_config;
+
+       s = &dev->subdevices[2];
+       /* analog output subdevice */
+       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;
+
+       s = &dev->subdevices[3];
+       /* digital i/o subdevice */
+       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 http://www.comedi.org");
+MODULE_DESCRIPTION("Comedi low-level driver");
+MODULE_LICENSE("GPL");