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