linux/drivers/comedi/drivers/ii_pci20kc.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * ii_pci20kc.c
   4 * Driver for Intelligent Instruments PCI-20001C carrier board and modules.
   5 *
   6 * Copyright (C) 2000 Markus Kempf <kempf@matsci.uni-sb.de>
   7 * with suggestions from David Schleef          16.06.2000
   8 */
   9
  10/*
  11 * Driver: ii_pci20kc
  12 * Description: Intelligent Instruments PCI-20001C carrier board
  13 * Devices: [Intelligent Instrumentation] PCI-20001C (ii_pci20kc)
  14 * Author: Markus Kempf <kempf@matsci.uni-sb.de>
  15 * Status: works
  16 *
  17 * Supports the PCI-20001C-1a and PCI-20001C-2a carrier boards. The
  18 * -2a version has 32 on-board DIO channels. Three add-on modules
  19 * can be added to the carrier board for additional functionality.
  20 *
  21 * Supported add-on modules:
  22 *      PCI-20006M-1   1 channel, 16-bit analog output module
  23 *      PCI-20006M-2   2 channel, 16-bit analog output module
  24 *      PCI-20341M-1A  4 channel, 16-bit analog input module
  25 *
  26 * Options:
  27 *   0   Board base address
  28 *   1   IRQ (not-used)
  29 */
  30
  31#include <linux/module.h>
  32#include <linux/io.h>
  33#include "../comedidev.h"
  34
  35/*
  36 * Register I/O map
  37 */
  38#define II20K_SIZE                      0x400
  39#define II20K_MOD_OFFSET                0x100
  40#define II20K_ID_REG                    0x00
  41#define II20K_ID_MOD1_EMPTY             BIT(7)
  42#define II20K_ID_MOD2_EMPTY             BIT(6)
  43#define II20K_ID_MOD3_EMPTY             BIT(5)
  44#define II20K_ID_MASK                   0x1f
  45#define II20K_ID_PCI20001C_1A           0x1b    /* no on-board DIO */
  46#define II20K_ID_PCI20001C_2A           0x1d    /* on-board DIO */
  47#define II20K_MOD_STATUS_REG            0x40
  48#define II20K_MOD_STATUS_IRQ_MOD1       BIT(7)
  49#define II20K_MOD_STATUS_IRQ_MOD2       BIT(6)
  50#define II20K_MOD_STATUS_IRQ_MOD3       BIT(5)
  51#define II20K_DIO0_REG                  0x80
  52#define II20K_DIO1_REG                  0x81
  53#define II20K_DIR_ENA_REG               0x82
  54#define II20K_DIR_DIO3_OUT              BIT(7)
  55#define II20K_DIR_DIO2_OUT              BIT(6)
  56#define II20K_BUF_DISAB_DIO3            BIT(5)
  57#define II20K_BUF_DISAB_DIO2            BIT(4)
  58#define II20K_DIR_DIO1_OUT              BIT(3)
  59#define II20K_DIR_DIO0_OUT              BIT(2)
  60#define II20K_BUF_DISAB_DIO1            BIT(1)
  61#define II20K_BUF_DISAB_DIO0            BIT(0)
  62#define II20K_CTRL01_REG                0x83
  63#define II20K_CTRL01_SET                BIT(7)
  64#define II20K_CTRL01_DIO0_IN            BIT(4)
  65#define II20K_CTRL01_DIO1_IN            BIT(1)
  66#define II20K_DIO2_REG                  0xc0
  67#define II20K_DIO3_REG                  0xc1
  68#define II20K_CTRL23_REG                0xc3
  69#define II20K_CTRL23_SET                BIT(7)
  70#define II20K_CTRL23_DIO2_IN            BIT(4)
  71#define II20K_CTRL23_DIO3_IN            BIT(1)
  72
  73#define II20K_ID_PCI20006M_1            0xe2    /* 1 AO channels */
  74#define II20K_ID_PCI20006M_2            0xe3    /* 2 AO channels */
  75#define II20K_AO_STRB_REG(x)            (0x0b + ((x) * 0x08))
  76#define II20K_AO_LSB_REG(x)             (0x0d + ((x) * 0x08))
  77#define II20K_AO_MSB_REG(x)             (0x0e + ((x) * 0x08))
  78#define II20K_AO_STRB_BOTH_REG          0x1b
  79
  80#define II20K_ID_PCI20341M_1            0x77    /* 4 AI channels */
  81#define II20K_AI_STATUS_CMD_REG         0x01
  82#define II20K_AI_STATUS_CMD_BUSY        BIT(7)
  83#define II20K_AI_STATUS_CMD_HW_ENA      BIT(1)
  84#define II20K_AI_STATUS_CMD_EXT_START   BIT(0)
  85#define II20K_AI_LSB_REG                0x02
  86#define II20K_AI_MSB_REG                0x03
  87#define II20K_AI_PACER_RESET_REG        0x04
  88#define II20K_AI_16BIT_DATA_REG         0x06
  89#define II20K_AI_CONF_REG               0x10
  90#define II20K_AI_CONF_ENA               BIT(2)
  91#define II20K_AI_OPT_REG                0x11
  92#define II20K_AI_OPT_TRIG_ENA           BIT(5)
  93#define II20K_AI_OPT_TRIG_INV           BIT(4)
  94#define II20K_AI_OPT_TIMEBASE(x)        (((x) & 0x3) << 1)
  95#define II20K_AI_OPT_BURST_MODE         BIT(0)
  96#define II20K_AI_STATUS_REG             0x12
  97#define II20K_AI_STATUS_INT             BIT(7)
  98#define II20K_AI_STATUS_TRIG            BIT(6)
  99#define II20K_AI_STATUS_TRIG_ENA        BIT(5)
 100#define II20K_AI_STATUS_PACER_ERR       BIT(2)
 101#define II20K_AI_STATUS_DATA_ERR        BIT(1)
 102#define II20K_AI_STATUS_SET_TIME_ERR    BIT(0)
 103#define II20K_AI_LAST_CHAN_ADDR_REG     0x13
 104#define II20K_AI_CUR_ADDR_REG           0x14
 105#define II20K_AI_SET_TIME_REG           0x15
 106#define II20K_AI_DELAY_LSB_REG          0x16
 107#define II20K_AI_DELAY_MSB_REG          0x17
 108#define II20K_AI_CHAN_ADV_REG           0x18
 109#define II20K_AI_CHAN_RESET_REG         0x19
 110#define II20K_AI_START_TRIG_REG         0x1a
 111#define II20K_AI_COUNT_RESET_REG        0x1b
 112#define II20K_AI_CHANLIST_REG           0x80
 113#define II20K_AI_CHANLIST_ONBOARD_ONLY  BIT(5)
 114#define II20K_AI_CHANLIST_GAIN(x)       (((x) & 0x3) << 3)
 115#define II20K_AI_CHANLIST_MUX_ENA       BIT(2)
 116#define II20K_AI_CHANLIST_CHAN(x)       (((x) & 0x3) << 0)
 117#define II20K_AI_CHANLIST_LEN           0x80
 118
 119/* the AO range is set by jumpers on the 20006M module */
 120static const struct comedi_lrange ii20k_ao_ranges = {
 121        3, {
 122                BIP_RANGE(5),   /* Chan 0 - W1/W3 in   Chan 1 - W2/W4 in  */
 123                UNI_RANGE(10),  /* Chan 0 - W1/W3 out  Chan 1 - W2/W4 in  */
 124                BIP_RANGE(10)   /* Chan 0 - W1/W3 in   Chan 1 - W2/W4 out */
 125        }
 126};
 127
 128static const struct comedi_lrange ii20k_ai_ranges = {
 129        4, {
 130                BIP_RANGE(5),           /* gain 1 */
 131                BIP_RANGE(0.5),         /* gain 10 */
 132                BIP_RANGE(0.05),        /* gain 100 */
 133                BIP_RANGE(0.025)        /* gain 200 */
 134        },
 135};
 136
 137static void __iomem *ii20k_module_iobase(struct comedi_device *dev,
 138                                         struct comedi_subdevice *s)
 139{
 140        return dev->mmio + (s->index + 1) * II20K_MOD_OFFSET;
 141}
 142
 143static int ii20k_ao_insn_write(struct comedi_device *dev,
 144                               struct comedi_subdevice *s,
 145                               struct comedi_insn *insn,
 146                               unsigned int *data)
 147{
 148        void __iomem *iobase = ii20k_module_iobase(dev, s);
 149        unsigned int chan = CR_CHAN(insn->chanspec);
 150        int i;
 151
 152        for (i = 0; i < insn->n; i++) {
 153                unsigned int val = data[i];
 154
 155                s->readback[chan] = val;
 156
 157                /* munge the offset binary data to 2's complement */
 158                val = comedi_offset_munge(s, val);
 159
 160                writeb(val & 0xff, iobase + II20K_AO_LSB_REG(chan));
 161                writeb((val >> 8) & 0xff, iobase + II20K_AO_MSB_REG(chan));
 162                writeb(0x00, iobase + II20K_AO_STRB_REG(chan));
 163        }
 164
 165        return insn->n;
 166}
 167
 168static int ii20k_ai_eoc(struct comedi_device *dev,
 169                        struct comedi_subdevice *s,
 170                        struct comedi_insn *insn,
 171                        unsigned long context)
 172{
 173        void __iomem *iobase = ii20k_module_iobase(dev, s);
 174        unsigned char status;
 175
 176        status = readb(iobase + II20K_AI_STATUS_REG);
 177        if ((status & II20K_AI_STATUS_INT) == 0)
 178                return 0;
 179        return -EBUSY;
 180}
 181
 182static void ii20k_ai_setup(struct comedi_device *dev,
 183                           struct comedi_subdevice *s,
 184                           unsigned int chanspec)
 185{
 186        void __iomem *iobase = ii20k_module_iobase(dev, s);
 187        unsigned int chan = CR_CHAN(chanspec);
 188        unsigned int range = CR_RANGE(chanspec);
 189        unsigned char val;
 190
 191        /* initialize module */
 192        writeb(II20K_AI_CONF_ENA, iobase + II20K_AI_CONF_REG);
 193
 194        /* software conversion */
 195        writeb(0, iobase + II20K_AI_STATUS_CMD_REG);
 196
 197        /* set the time base for the settling time counter based on the gain */
 198        val = (range < 3) ? II20K_AI_OPT_TIMEBASE(0) : II20K_AI_OPT_TIMEBASE(2);
 199        writeb(val, iobase + II20K_AI_OPT_REG);
 200
 201        /* set the settling time counter based on the gain */
 202        val = (range < 2) ? 0x58 : (range < 3) ? 0x93 : 0x99;
 203        writeb(val, iobase + II20K_AI_SET_TIME_REG);
 204
 205        /* set number of input channels */
 206        writeb(1, iobase + II20K_AI_LAST_CHAN_ADDR_REG);
 207
 208        /* set the channel list byte */
 209        val = II20K_AI_CHANLIST_ONBOARD_ONLY |
 210              II20K_AI_CHANLIST_MUX_ENA |
 211              II20K_AI_CHANLIST_GAIN(range) |
 212              II20K_AI_CHANLIST_CHAN(chan);
 213        writeb(val, iobase + II20K_AI_CHANLIST_REG);
 214
 215        /* reset settling time counter and trigger delay counter */
 216        writeb(0, iobase + II20K_AI_COUNT_RESET_REG);
 217
 218        /* reset channel scanner */
 219        writeb(0, iobase + II20K_AI_CHAN_RESET_REG);
 220}
 221
 222static int ii20k_ai_insn_read(struct comedi_device *dev,
 223                              struct comedi_subdevice *s,
 224                              struct comedi_insn *insn,
 225                              unsigned int *data)
 226{
 227        void __iomem *iobase = ii20k_module_iobase(dev, s);
 228        int ret;
 229        int i;
 230
 231        ii20k_ai_setup(dev, s, insn->chanspec);
 232
 233        for (i = 0; i < insn->n; i++) {
 234                unsigned int val;
 235
 236                /* generate a software start convert signal */
 237                readb(iobase + II20K_AI_PACER_RESET_REG);
 238
 239                ret = comedi_timeout(dev, s, insn, ii20k_ai_eoc, 0);
 240                if (ret)
 241                        return ret;
 242
 243                val = readb(iobase + II20K_AI_LSB_REG);
 244                val |= (readb(iobase + II20K_AI_MSB_REG) << 8);
 245
 246                /* munge the 2's complement data to offset binary */
 247                data[i] = comedi_offset_munge(s, val);
 248        }
 249
 250        return insn->n;
 251}
 252
 253static void ii20k_dio_config(struct comedi_device *dev,
 254                             struct comedi_subdevice *s)
 255{
 256        unsigned char ctrl01 = 0;
 257        unsigned char ctrl23 = 0;
 258        unsigned char dir_ena = 0;
 259
 260        /* port 0 - channels 0-7 */
 261        if (s->io_bits & 0x000000ff) {
 262                /* output port */
 263                ctrl01 &= ~II20K_CTRL01_DIO0_IN;
 264                dir_ena &= ~II20K_BUF_DISAB_DIO0;
 265                dir_ena |= II20K_DIR_DIO0_OUT;
 266        } else {
 267                /* input port */
 268                ctrl01 |= II20K_CTRL01_DIO0_IN;
 269                dir_ena &= ~II20K_DIR_DIO0_OUT;
 270        }
 271
 272        /* port 1 - channels 8-15 */
 273        if (s->io_bits & 0x0000ff00) {
 274                /* output port */
 275                ctrl01 &= ~II20K_CTRL01_DIO1_IN;
 276                dir_ena &= ~II20K_BUF_DISAB_DIO1;
 277                dir_ena |= II20K_DIR_DIO1_OUT;
 278        } else {
 279                /* input port */
 280                ctrl01 |= II20K_CTRL01_DIO1_IN;
 281                dir_ena &= ~II20K_DIR_DIO1_OUT;
 282        }
 283
 284        /* port 2 - channels 16-23 */
 285        if (s->io_bits & 0x00ff0000) {
 286                /* output port */
 287                ctrl23 &= ~II20K_CTRL23_DIO2_IN;
 288                dir_ena &= ~II20K_BUF_DISAB_DIO2;
 289                dir_ena |= II20K_DIR_DIO2_OUT;
 290        } else {
 291                /* input port */
 292                ctrl23 |= II20K_CTRL23_DIO2_IN;
 293                dir_ena &= ~II20K_DIR_DIO2_OUT;
 294        }
 295
 296        /* port 3 - channels 24-31 */
 297        if (s->io_bits & 0xff000000) {
 298                /* output port */
 299                ctrl23 &= ~II20K_CTRL23_DIO3_IN;
 300                dir_ena &= ~II20K_BUF_DISAB_DIO3;
 301                dir_ena |= II20K_DIR_DIO3_OUT;
 302        } else {
 303                /* input port */
 304                ctrl23 |= II20K_CTRL23_DIO3_IN;
 305                dir_ena &= ~II20K_DIR_DIO3_OUT;
 306        }
 307
 308        ctrl23 |= II20K_CTRL01_SET;
 309        ctrl23 |= II20K_CTRL23_SET;
 310
 311        /* order is important */
 312        writeb(ctrl01, dev->mmio + II20K_CTRL01_REG);
 313        writeb(ctrl23, dev->mmio + II20K_CTRL23_REG);
 314        writeb(dir_ena, dev->mmio + II20K_DIR_ENA_REG);
 315}
 316
 317static int ii20k_dio_insn_config(struct comedi_device *dev,
 318                                 struct comedi_subdevice *s,
 319                                 struct comedi_insn *insn,
 320                                 unsigned int *data)
 321{
 322        unsigned int chan = CR_CHAN(insn->chanspec);
 323        unsigned int mask;
 324        int ret;
 325
 326        if (chan < 8)
 327                mask = 0x000000ff;
 328        else if (chan < 16)
 329                mask = 0x0000ff00;
 330        else if (chan < 24)
 331                mask = 0x00ff0000;
 332        else
 333                mask = 0xff000000;
 334
 335        ret = comedi_dio_insn_config(dev, s, insn, data, mask);
 336        if (ret)
 337                return ret;
 338
 339        ii20k_dio_config(dev, s);
 340
 341        return insn->n;
 342}
 343
 344static int ii20k_dio_insn_bits(struct comedi_device *dev,
 345                               struct comedi_subdevice *s,
 346                               struct comedi_insn *insn,
 347                               unsigned int *data)
 348{
 349        unsigned int mask;
 350
 351        mask = comedi_dio_update_state(s, data);
 352        if (mask) {
 353                if (mask & 0x000000ff)
 354                        writeb((s->state >> 0) & 0xff,
 355                               dev->mmio + II20K_DIO0_REG);
 356                if (mask & 0x0000ff00)
 357                        writeb((s->state >> 8) & 0xff,
 358                               dev->mmio + II20K_DIO1_REG);
 359                if (mask & 0x00ff0000)
 360                        writeb((s->state >> 16) & 0xff,
 361                               dev->mmio + II20K_DIO2_REG);
 362                if (mask & 0xff000000)
 363                        writeb((s->state >> 24) & 0xff,
 364                               dev->mmio + II20K_DIO3_REG);
 365        }
 366
 367        data[1] = readb(dev->mmio + II20K_DIO0_REG);
 368        data[1] |= readb(dev->mmio + II20K_DIO1_REG) << 8;
 369        data[1] |= readb(dev->mmio + II20K_DIO2_REG) << 16;
 370        data[1] |= readb(dev->mmio + II20K_DIO3_REG) << 24;
 371
 372        return insn->n;
 373}
 374
 375static int ii20k_init_module(struct comedi_device *dev,
 376                             struct comedi_subdevice *s)
 377{
 378        void __iomem *iobase = ii20k_module_iobase(dev, s);
 379        unsigned char id;
 380        int ret;
 381
 382        id = readb(iobase + II20K_ID_REG);
 383        switch (id) {
 384        case II20K_ID_PCI20006M_1:
 385        case II20K_ID_PCI20006M_2:
 386                /* Analog Output subdevice */
 387                s->type         = COMEDI_SUBD_AO;
 388                s->subdev_flags = SDF_WRITABLE;
 389                s->n_chan       = (id == II20K_ID_PCI20006M_2) ? 2 : 1;
 390                s->maxdata      = 0xffff;
 391                s->range_table  = &ii20k_ao_ranges;
 392                s->insn_write   = ii20k_ao_insn_write;
 393
 394                ret = comedi_alloc_subdev_readback(s);
 395                if (ret)
 396                        return ret;
 397                break;
 398        case II20K_ID_PCI20341M_1:
 399                /* Analog Input subdevice */
 400                s->type         = COMEDI_SUBD_AI;
 401                s->subdev_flags = SDF_READABLE | SDF_DIFF;
 402                s->n_chan       = 4;
 403                s->maxdata      = 0xffff;
 404                s->range_table  = &ii20k_ai_ranges;
 405                s->insn_read    = ii20k_ai_insn_read;
 406                break;
 407        default:
 408                s->type = COMEDI_SUBD_UNUSED;
 409                break;
 410        }
 411
 412        return 0;
 413}
 414
 415static int ii20k_attach(struct comedi_device *dev,
 416                        struct comedi_devconfig *it)
 417{
 418        struct comedi_subdevice *s;
 419        unsigned int membase;
 420        unsigned char id;
 421        bool has_dio;
 422        int ret;
 423
 424        membase = it->options[0];
 425        if (!membase || (membase & ~(0x100000 - II20K_SIZE))) {
 426                dev_warn(dev->class_dev,
 427                         "%s: invalid memory address specified\n",
 428                         dev->board_name);
 429                return -EINVAL;
 430        }
 431
 432        if (!request_mem_region(membase, II20K_SIZE, dev->board_name)) {
 433                dev_warn(dev->class_dev, "%s: I/O mem conflict (%#x,%u)\n",
 434                         dev->board_name, membase, II20K_SIZE);
 435                return -EIO;
 436        }
 437        dev->iobase = membase;  /* actually, a memory address */
 438
 439        dev->mmio = ioremap(membase, II20K_SIZE);
 440        if (!dev->mmio)
 441                return -ENOMEM;
 442
 443        id = readb(dev->mmio + II20K_ID_REG);
 444        switch (id & II20K_ID_MASK) {
 445        case II20K_ID_PCI20001C_1A:
 446                has_dio = false;
 447                break;
 448        case II20K_ID_PCI20001C_2A:
 449                has_dio = true;
 450                break;
 451        default:
 452                return -ENODEV;
 453        }
 454
 455        ret = comedi_alloc_subdevices(dev, 4);
 456        if (ret)
 457                return ret;
 458
 459        s = &dev->subdevices[0];
 460        if (id & II20K_ID_MOD1_EMPTY) {
 461                s->type = COMEDI_SUBD_UNUSED;
 462        } else {
 463                ret = ii20k_init_module(dev, s);
 464                if (ret)
 465                        return ret;
 466        }
 467
 468        s = &dev->subdevices[1];
 469        if (id & II20K_ID_MOD2_EMPTY) {
 470                s->type = COMEDI_SUBD_UNUSED;
 471        } else {
 472                ret = ii20k_init_module(dev, s);
 473                if (ret)
 474                        return ret;
 475        }
 476
 477        s = &dev->subdevices[2];
 478        if (id & II20K_ID_MOD3_EMPTY) {
 479                s->type = COMEDI_SUBD_UNUSED;
 480        } else {
 481                ret = ii20k_init_module(dev, s);
 482                if (ret)
 483                        return ret;
 484        }
 485
 486        /* Digital I/O subdevice */
 487        s = &dev->subdevices[3];
 488        if (has_dio) {
 489                s->type         = COMEDI_SUBD_DIO;
 490                s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
 491                s->n_chan       = 32;
 492                s->maxdata      = 1;
 493                s->range_table  = &range_digital;
 494                s->insn_bits    = ii20k_dio_insn_bits;
 495                s->insn_config  = ii20k_dio_insn_config;
 496
 497                /* default all channels to input */
 498                ii20k_dio_config(dev, s);
 499        } else {
 500                s->type = COMEDI_SUBD_UNUSED;
 501        }
 502
 503        return 0;
 504}
 505
 506static void ii20k_detach(struct comedi_device *dev)
 507{
 508        if (dev->mmio)
 509                iounmap(dev->mmio);
 510        if (dev->iobase)        /* actually, a memory address */
 511                release_mem_region(dev->iobase, II20K_SIZE);
 512}
 513
 514static struct comedi_driver ii20k_driver = {
 515        .driver_name    = "ii_pci20kc",
 516        .module         = THIS_MODULE,
 517        .attach         = ii20k_attach,
 518        .detach         = ii20k_detach,
 519};
 520module_comedi_driver(ii20k_driver);
 521
 522MODULE_AUTHOR("Comedi https://www.comedi.org");
 523MODULE_DESCRIPTION("Comedi driver for Intelligent Instruments PCI-20001C");
 524MODULE_LICENSE("GPL");
 525