linux/drivers/staging/comedi/drivers/multiq3.c
<<
>>
Prefs
   1/*
   2 * multiq3.c
   3 * Hardware driver for Quanser Consulting MultiQ-3 board
   4 *
   5 * COMEDI - Linux Control and Measurement Device Interface
   6 * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License as published by
  10 * the Free Software Foundation; either version 2 of the License, or
  11 * (at your option) any later version.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 */
  18
  19/*
  20 * Driver: multiq3
  21 * Description: Quanser Consulting MultiQ-3
  22 * Devices: [Quanser Consulting] MultiQ-3 (multiq3)
  23 * Author: Anders Blomdell <anders.blomdell@control.lth.se>
  24 * Status: works
  25 *
  26 * Configuration Options:
  27 *  [0] - I/O port base address
  28 *  [1] - IRQ (not used)
  29 *  [2] - Number of optional encoder chips installed on board
  30 *        0 = none
  31 *        1 = 2 inputs (Model -2E)
  32 *        2 = 4 inputs (Model -4E)
  33 *        3 = 6 inputs (Model -6E)
  34 *        4 = 8 inputs (Model -8E)
  35 */
  36
  37#include <linux/module.h>
  38
  39#include "../comedidev.h"
  40
  41/*
  42 * Register map
  43 */
  44#define MULTIQ3_DI_REG                  0x00
  45#define MULTIQ3_DO_REG                  0x00
  46#define MULTIQ3_AO_REG                  0x02
  47#define MULTIQ3_AI_REG                  0x04
  48#define MULTIQ3_AI_CONV_REG             0x04
  49#define MULTIQ3_STATUS_REG              0x06
  50#define MULTIQ3_STATUS_EOC              BIT(3)
  51#define MULTIQ3_STATUS_EOC_I            BIT(4)
  52#define MULTIQ3_CTRL_REG                0x06
  53#define MULTIQ3_CTRL_AO_CHAN(x)         (((x) & 0x7) << 0)
  54#define MULTIQ3_CTRL_RC(x)              (((x) & 0x3) << 0)
  55#define MULTIQ3_CTRL_AI_CHAN(x)         (((x) & 0x7) << 3)
  56#define MULTIQ3_CTRL_E_CHAN(x)          (((x) & 0x7) << 3)
  57#define MULTIQ3_CTRL_EN                 BIT(6)
  58#define MULTIQ3_CTRL_AZ                 BIT(7)
  59#define MULTIQ3_CTRL_CAL                BIT(8)
  60#define MULTIQ3_CTRL_SH                 BIT(9)
  61#define MULTIQ3_CTRL_CLK                BIT(10)
  62#define MULTIQ3_CTRL_LD                 (3 << 11)
  63#define MULTIQ3_CLK_REG                 0x08
  64#define MULTIQ3_ENC_DATA_REG            0x0c
  65#define MULTIQ3_ENC_CTRL_REG            0x0e
  66
  67/*
  68 * Encoder chip commands (from the programming manual)
  69 */
  70#define MULTIQ3_CLOCK_DATA              0x00    /* FCK frequency divider */
  71#define MULTIQ3_CLOCK_SETUP             0x18    /* xfer PR0 to PSC */
  72#define MULTIQ3_INPUT_SETUP             0x41    /* enable inputs A and B */
  73#define MULTIQ3_QUAD_X4                 0x38    /* quadrature */
  74#define MULTIQ3_BP_RESET                0x01    /* reset byte pointer */
  75#define MULTIQ3_CNTR_RESET              0x02    /* reset counter */
  76#define MULTIQ3_TRSFRPR_CTR             0x08    /* xfre preset reg to counter */
  77#define MULTIQ3_TRSFRCNTR_OL            0x10    /* xfer CNTR to OL (x and y) */
  78#define MULTIQ3_EFLAG_RESET             0x06    /* reset E bit of flag reg */
  79
  80static void multiq3_set_ctrl(struct comedi_device *dev, unsigned int bits)
  81{
  82        /*
  83         * According to the programming manual, the SH and CLK bits should
  84         * be kept high at all times.
  85         */
  86        outw(MULTIQ3_CTRL_SH | MULTIQ3_CTRL_CLK | bits,
  87             dev->iobase + MULTIQ3_CTRL_REG);
  88}
  89
  90static int multiq3_ai_status(struct comedi_device *dev,
  91                             struct comedi_subdevice *s,
  92                             struct comedi_insn *insn,
  93                             unsigned long context)
  94{
  95        unsigned int status;
  96
  97        status = inw(dev->iobase + MULTIQ3_STATUS_REG);
  98        if (status & context)
  99                return 0;
 100        return -EBUSY;
 101}
 102
 103static int multiq3_ai_insn_read(struct comedi_device *dev,
 104                                struct comedi_subdevice *s,
 105                                struct comedi_insn *insn,
 106                                unsigned int *data)
 107{
 108        unsigned int chan = CR_CHAN(insn->chanspec);
 109        unsigned int val;
 110        int ret;
 111        int i;
 112
 113        multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_AI_CHAN(chan));
 114
 115        ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
 116                             MULTIQ3_STATUS_EOC);
 117        if (ret)
 118                return ret;
 119
 120        for (i = 0; i < insn->n; i++) {
 121                outw(0, dev->iobase + MULTIQ3_AI_CONV_REG);
 122
 123                ret = comedi_timeout(dev, s, insn, multiq3_ai_status,
 124                                     MULTIQ3_STATUS_EOC_I);
 125                if (ret)
 126                        return ret;
 127
 128                /* get a 16-bit sample; mask it to the subdevice resolution */
 129                val = inb(dev->iobase + MULTIQ3_AI_REG) << 8;
 130                val |= inb(dev->iobase + MULTIQ3_AI_REG);
 131                val &= s->maxdata;
 132
 133                /* munge the 2's complement value to offset binary */
 134                data[i] = comedi_offset_munge(s, val);
 135        }
 136
 137        return insn->n;
 138}
 139
 140static int multiq3_ao_insn_write(struct comedi_device *dev,
 141                                 struct comedi_subdevice *s,
 142                                 struct comedi_insn *insn,
 143                                 unsigned int *data)
 144{
 145        unsigned int chan = CR_CHAN(insn->chanspec);
 146        unsigned int val = s->readback[chan];
 147        int i;
 148
 149        for (i = 0; i < insn->n; i++) {
 150                val = data[i];
 151                multiq3_set_ctrl(dev, MULTIQ3_CTRL_LD |
 152                                      MULTIQ3_CTRL_AO_CHAN(chan));
 153                outw(val, dev->iobase + MULTIQ3_AO_REG);
 154                multiq3_set_ctrl(dev, 0);
 155        }
 156        s->readback[chan] = val;
 157
 158        return insn->n;
 159}
 160
 161static int multiq3_di_insn_bits(struct comedi_device *dev,
 162                                struct comedi_subdevice *s,
 163                                struct comedi_insn *insn, unsigned int *data)
 164{
 165        data[1] = inw(dev->iobase + MULTIQ3_DI_REG);
 166
 167        return insn->n;
 168}
 169
 170static int multiq3_do_insn_bits(struct comedi_device *dev,
 171                                struct comedi_subdevice *s,
 172                                struct comedi_insn *insn,
 173                                unsigned int *data)
 174{
 175        if (comedi_dio_update_state(s, data))
 176                outw(s->state, dev->iobase + MULTIQ3_DO_REG);
 177
 178        data[1] = s->state;
 179
 180        return insn->n;
 181}
 182
 183static int multiq3_encoder_insn_read(struct comedi_device *dev,
 184                                     struct comedi_subdevice *s,
 185                                     struct comedi_insn *insn,
 186                                     unsigned int *data)
 187{
 188        unsigned int chan = CR_CHAN(insn->chanspec);
 189        unsigned int val;
 190        int i;
 191
 192        for (i = 0; i < insn->n; i++) {
 193                /* select encoder channel */
 194                multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN |
 195                                      MULTIQ3_CTRL_E_CHAN(chan));
 196
 197                /* reset the byte pointer */
 198                outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
 199
 200                /* latch the data */
 201                outb(MULTIQ3_TRSFRCNTR_OL, dev->iobase + MULTIQ3_ENC_CTRL_REG);
 202
 203                /* read the 24-bit encoder data (lsb/mid/msb) */
 204                val = inb(dev->iobase + MULTIQ3_ENC_DATA_REG);
 205                val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 8);
 206                val |= (inb(dev->iobase + MULTIQ3_ENC_DATA_REG) << 16);
 207
 208                /*
 209                 * Munge the data so that the reset value is in the middle
 210                 * of the maxdata range, i.e.:
 211                 *
 212                 * real value   comedi value
 213                 * 0xffffff     0x7fffff        1 negative count
 214                 * 0x000000     0x800000        reset value
 215                 * 0x000001     0x800001        1 positive count
 216                 *
 217                 * It's possible for the 24-bit counter to overflow but it
 218                 * would normally take _quite_ a few turns. A 2000 line
 219                 * encoder in quadrature results in 8000 counts/rev. So about
 220                 * 1048 turns in either direction can be measured without
 221                 * an overflow.
 222                 */
 223                data[i] = (val + ((s->maxdata + 1) >> 1)) & s->maxdata;
 224        }
 225
 226        return insn->n;
 227}
 228
 229static void multiq3_encoder_reset(struct comedi_device *dev,
 230                                  unsigned int chan)
 231{
 232        multiq3_set_ctrl(dev, MULTIQ3_CTRL_EN | MULTIQ3_CTRL_E_CHAN(chan));
 233        outb(MULTIQ3_EFLAG_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
 234        outb(MULTIQ3_BP_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
 235        outb(MULTIQ3_CLOCK_DATA, dev->iobase + MULTIQ3_ENC_DATA_REG);
 236        outb(MULTIQ3_CLOCK_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG);
 237        outb(MULTIQ3_INPUT_SETUP, dev->iobase + MULTIQ3_ENC_CTRL_REG);
 238        outb(MULTIQ3_QUAD_X4, dev->iobase + MULTIQ3_ENC_CTRL_REG);
 239        outb(MULTIQ3_CNTR_RESET, dev->iobase + MULTIQ3_ENC_CTRL_REG);
 240}
 241
 242static int multiq3_encoder_insn_config(struct comedi_device *dev,
 243                                       struct comedi_subdevice *s,
 244                                       struct comedi_insn *insn,
 245                                       unsigned int *data)
 246{
 247        unsigned int chan = CR_CHAN(insn->chanspec);
 248
 249        switch (data[0]) {
 250        case INSN_CONFIG_RESET:
 251                multiq3_encoder_reset(dev, chan);
 252                break;
 253        default:
 254                return -EINVAL;
 255        }
 256
 257        return insn->n;
 258}
 259
 260static int multiq3_attach(struct comedi_device *dev,
 261                          struct comedi_devconfig *it)
 262{
 263        struct comedi_subdevice *s;
 264        int ret;
 265        int i;
 266
 267        ret = comedi_request_region(dev, it->options[0], 0x10);
 268        if (ret)
 269                return ret;
 270
 271        ret = comedi_alloc_subdevices(dev, 5);
 272        if (ret)
 273                return ret;
 274
 275        /* Analog Input subdevice */
 276        s = &dev->subdevices[0];
 277        s->type         = COMEDI_SUBD_AI;
 278        s->subdev_flags = SDF_READABLE | SDF_GROUND;
 279        s->n_chan       = 8;
 280        s->maxdata      = 0x1fff;
 281        s->range_table  = &range_bipolar5;
 282        s->insn_read    = multiq3_ai_insn_read;
 283
 284        /* Analog Output subdevice */
 285        s = &dev->subdevices[1];
 286        s->type         = COMEDI_SUBD_AO;
 287        s->subdev_flags = SDF_WRITABLE;
 288        s->n_chan       = 8;
 289        s->maxdata      = 0x0fff;
 290        s->range_table  = &range_bipolar5;
 291        s->insn_write   = multiq3_ao_insn_write;
 292
 293        ret = comedi_alloc_subdev_readback(s);
 294        if (ret)
 295                return ret;
 296
 297        /* Digital Input subdevice */
 298        s = &dev->subdevices[2];
 299        s->type         = COMEDI_SUBD_DI;
 300        s->subdev_flags = SDF_READABLE;
 301        s->n_chan       = 16;
 302        s->maxdata      = 1;
 303        s->range_table  = &range_digital;
 304        s->insn_bits    = multiq3_di_insn_bits;
 305
 306        /* Digital Output subdevice */
 307        s = &dev->subdevices[3];
 308        s->type         = COMEDI_SUBD_DO;
 309        s->subdev_flags = SDF_WRITABLE;
 310        s->n_chan       = 16;
 311        s->maxdata      = 1;
 312        s->range_table  = &range_digital;
 313        s->insn_bits    = multiq3_do_insn_bits;
 314
 315        /* Encoder (Counter) subdevice */
 316        s = &dev->subdevices[4];
 317        s->type         = COMEDI_SUBD_COUNTER;
 318        s->subdev_flags = SDF_READABLE | SDF_LSAMPL;
 319        s->n_chan       = it->options[2] * 2;
 320        s->maxdata      = 0x00ffffff;
 321        s->range_table  = &range_unknown;
 322        s->insn_read    = multiq3_encoder_insn_read;
 323        s->insn_config  = multiq3_encoder_insn_config;
 324
 325        for (i = 0; i < s->n_chan; i++)
 326                multiq3_encoder_reset(dev, i);
 327
 328        return 0;
 329}
 330
 331static struct comedi_driver multiq3_driver = {
 332        .driver_name    = "multiq3",
 333        .module         = THIS_MODULE,
 334        .attach         = multiq3_attach,
 335        .detach         = comedi_legacy_detach,
 336};
 337module_comedi_driver(multiq3_driver);
 338
 339MODULE_AUTHOR("Comedi http://www.comedi.org");
 340MODULE_DESCRIPTION("Comedi driver for Quanser Consulting MultiQ-3 board");
 341MODULE_LICENSE("GPL");
 342