linux/drivers/comedi/drivers/adl_pci7x3x.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * COMEDI driver for the ADLINK PCI-723x/743x series boards.
   4 * Copyright (C) 2012 H Hartley Sweeten <hsweeten@visionengravers.com>
   5 *
   6 * Based on the adl_pci7230 driver written by:
   7 *      David Fernandez <dfcastelao@gmail.com>
   8 * and the adl_pci7432 driver written by:
   9 *      Michel Lachaine <mike@mikelachaine.ca>
  10 *
  11 * COMEDI - Linux Control and Measurement Device Interface
  12 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
  13 */
  14
  15/*
  16 * Driver: adl_pci7x3x
  17 * Description: 32/64-Channel Isolated Digital I/O Boards
  18 * Devices: [ADLink] PCI-7230 (adl_pci7230), PCI-7233 (adl_pci7233),
  19 *   PCI-7234 (adl_pci7234), PCI-7432 (adl_pci7432), PCI-7433 (adl_pci7433),
  20 *   PCI-7434 (adl_pci7434)
  21 * Author: H Hartley Sweeten <hsweeten@visionengravers.com>
  22 * Updated: Fri, 20 Nov 2020 14:49:36 +0000
  23 * Status: works (tested on PCI-7230)
  24 *
  25 * One or two subdevices are setup by this driver depending on
  26 * the number of digital inputs and/or outputs provided by the
  27 * board. Each subdevice has a maximum of 32 channels.
  28 *
  29 *      PCI-7230 - 4 subdevices: 0 - 16 input, 1 - 16 output,
  30 *                               2 - IRQ_IDI0, 3 - IRQ_IDI1
  31 *      PCI-7233 - 1 subdevice: 0 - 32 input
  32 *      PCI-7234 - 1 subdevice: 0 - 32 output
  33 *      PCI-7432 - 2 subdevices: 0 - 32 input, 1 - 32 output
  34 *      PCI-7433 - 2 subdevices: 0 - 32 input, 1 - 32 input
  35 *      PCI-7434 - 2 subdevices: 0 - 32 output, 1 - 32 output
  36 *
  37 * The PCI-7230, PCI-7432 and PCI-7433 boards also support external
  38 * interrupt signals on digital input channels 0 and 1. The PCI-7233
  39 * has dual-interrupt sources for change-of-state (COS) on any 16
  40 * digital input channels of LSB and for COS on any 16 digital input
  41 * lines of MSB.
  42 *
  43 * Currently, this driver only supports interrupts for PCI-7230.
  44 *
  45 * Configuration Options: not applicable, uses comedi PCI auto config
  46 */
  47
  48#include <linux/module.h>
  49
  50#include "../comedi_pci.h"
  51
  52#include "plx9052.h"
  53
  54/*
  55 * Register I/O map (32-bit access only)
  56 */
  57#define PCI7X3X_DIO_REG         0x0000  /* in the DigIO Port area */
  58#define PCI743X_DIO_REG         0x0004
  59
  60#define ADL_PT_CLRIRQ           0x0040  /* in the DigIO Port area */
  61
  62#define LINTI1_EN_ACT_IDI0 (PLX9052_INTCSR_LI1ENAB | PLX9052_INTCSR_LI1STAT)
  63#define LINTI2_EN_ACT_IDI1 (PLX9052_INTCSR_LI2ENAB | PLX9052_INTCSR_LI2STAT)
  64#define EN_PCI_LINT2H_LINT1H    \
  65        (PLX9052_INTCSR_PCIENAB | PLX9052_INTCSR_LI2POL | PLX9052_INTCSR_LI1POL)
  66
  67enum adl_pci7x3x_boardid {
  68        BOARD_PCI7230,
  69        BOARD_PCI7233,
  70        BOARD_PCI7234,
  71        BOARD_PCI7432,
  72        BOARD_PCI7433,
  73        BOARD_PCI7434,
  74};
  75
  76struct adl_pci7x3x_boardinfo {
  77        const char *name;
  78        int nsubdevs;
  79        int di_nchan;
  80        int do_nchan;
  81        int irq_nchan;
  82};
  83
  84static const struct adl_pci7x3x_boardinfo adl_pci7x3x_boards[] = {
  85        [BOARD_PCI7230] = {
  86                .name           = "adl_pci7230",
  87                .nsubdevs       = 4,  /* IDI, IDO, IRQ_IDI0, IRQ_IDI1 */
  88                .di_nchan       = 16,
  89                .do_nchan       = 16,
  90                .irq_nchan      = 2,
  91        },
  92        [BOARD_PCI7233] = {
  93                .name           = "adl_pci7233",
  94                .nsubdevs       = 1,
  95                .di_nchan       = 32,
  96        },
  97        [BOARD_PCI7234] = {
  98                .name           = "adl_pci7234",
  99                .nsubdevs       = 1,
 100                .do_nchan       = 32,
 101        },
 102        [BOARD_PCI7432] = {
 103                .name           = "adl_pci7432",
 104                .nsubdevs       = 2,
 105                .di_nchan       = 32,
 106                .do_nchan       = 32,
 107        },
 108        [BOARD_PCI7433] = {
 109                .name           = "adl_pci7433",
 110                .nsubdevs       = 2,
 111                .di_nchan       = 64,
 112        },
 113        [BOARD_PCI7434] = {
 114                .name           = "adl_pci7434",
 115                .nsubdevs       = 2,
 116                .do_nchan       = 64,
 117        }
 118};
 119
 120struct adl_pci7x3x_dev_private_data {
 121        unsigned long lcr_io_base;
 122        unsigned int int_ctrl;
 123};
 124
 125struct adl_pci7x3x_sd_private_data {
 126        spinlock_t subd_slock;          /* spin-lock for cmd_running */
 127        unsigned long port_offset;
 128        short int cmd_running;
 129};
 130
 131static void process_irq(struct comedi_device *dev, unsigned int subdev,
 132                        unsigned short intcsr)
 133{
 134        struct comedi_subdevice *s = &dev->subdevices[subdev];
 135        struct adl_pci7x3x_sd_private_data *sd_priv = s->private;
 136        unsigned long reg = sd_priv->port_offset;
 137        struct comedi_async *async_p = s->async;
 138
 139        if (async_p) {
 140                unsigned short val = inw(dev->iobase + reg);
 141
 142                spin_lock(&sd_priv->subd_slock);
 143                if (sd_priv->cmd_running)
 144                        comedi_buf_write_samples(s, &val, 1);
 145                spin_unlock(&sd_priv->subd_slock);
 146                comedi_handle_events(dev, s);
 147        }
 148}
 149
 150static irqreturn_t adl_pci7x3x_interrupt(int irq, void *p_device)
 151{
 152        struct comedi_device *dev = p_device;
 153        struct adl_pci7x3x_dev_private_data *dev_private = dev->private;
 154        unsigned long cpu_flags;
 155        unsigned int intcsr;
 156        bool li1stat, li2stat;
 157
 158        if (!dev->attached) {
 159                /* Ignore interrupt before device fully attached. */
 160                /* Might not even have allocated subdevices yet! */
 161                return IRQ_NONE;
 162        }
 163
 164        /* Check if we are source of interrupt */
 165        spin_lock_irqsave(&dev->spinlock, cpu_flags);
 166        intcsr = inl(dev_private->lcr_io_base + PLX9052_INTCSR);
 167        li1stat = (intcsr & LINTI1_EN_ACT_IDI0) == LINTI1_EN_ACT_IDI0;
 168        li2stat = (intcsr & LINTI2_EN_ACT_IDI1) == LINTI2_EN_ACT_IDI1;
 169        if (li1stat || li2stat) {
 170                /* clear all current interrupt flags */
 171                /* Fixme: Reset all 2 Int Flags */
 172                outb(0x00, dev->iobase + ADL_PT_CLRIRQ);
 173        }
 174        spin_unlock_irqrestore(&dev->spinlock, cpu_flags);
 175
 176        /* SubDev 2, 3 = Isolated DigIn , on "SCSI2" jack!*/
 177
 178        if (li1stat)    /* 0x0005 LINTi1 is Enabled && IDI0 is 1 */
 179                process_irq(dev, 2, intcsr);
 180
 181        if (li2stat)    /* 0x0028 LINTi2 is Enabled && IDI1 is 1 */
 182                process_irq(dev, 3, intcsr);
 183
 184        return IRQ_RETVAL(li1stat || li2stat);
 185}
 186
 187static int adl_pci7x3x_asy_cmdtest(struct comedi_device *dev,
 188                                   struct comedi_subdevice *s,
 189                                   struct comedi_cmd *cmd)
 190{
 191        int err = 0;
 192
 193        /* Step 1 : check if triggers are trivially valid */
 194
 195        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
 196        err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
 197        err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
 198        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 199        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
 200
 201        if (err)
 202                return 1;
 203
 204        /* Step 2a : make sure trigger sources are unique */
 205        /* Step 2b : and mutually compatible */
 206
 207        /* Step 3: check if arguments are trivially valid */
 208
 209        err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
 210        err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
 211        err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
 212        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
 213                                           cmd->chanlist_len);
 214        err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
 215
 216        if (err)
 217                return 3;
 218
 219        /* Step 4: fix up any arguments */
 220
 221        /* Step 5: check channel list if it exists */
 222
 223        return 0;
 224}
 225
 226static int adl_pci7x3x_asy_cmd(struct comedi_device *dev,
 227                               struct comedi_subdevice *s)
 228{
 229        struct adl_pci7x3x_dev_private_data *dev_private = dev->private;
 230        struct adl_pci7x3x_sd_private_data *sd_priv = s->private;
 231        unsigned long cpu_flags;
 232        unsigned int int_enab;
 233
 234        if (s->index == 2) {
 235                /* enable LINTi1 == IDI sdi[0] Ch 0 IRQ ActHigh */
 236                int_enab = PLX9052_INTCSR_LI1ENAB;
 237        } else {
 238                /* enable LINTi2 == IDI sdi[0] Ch 1 IRQ ActHigh */
 239                int_enab = PLX9052_INTCSR_LI2ENAB;
 240        }
 241
 242        spin_lock_irqsave(&dev->spinlock, cpu_flags);
 243        dev_private->int_ctrl |= int_enab;
 244        outl(dev_private->int_ctrl, dev_private->lcr_io_base + PLX9052_INTCSR);
 245        spin_unlock_irqrestore(&dev->spinlock, cpu_flags);
 246
 247        spin_lock_irqsave(&sd_priv->subd_slock, cpu_flags);
 248        sd_priv->cmd_running = 1;
 249        spin_unlock_irqrestore(&sd_priv->subd_slock, cpu_flags);
 250
 251        return 0;
 252}
 253
 254static int adl_pci7x3x_asy_cancel(struct comedi_device *dev,
 255                                  struct comedi_subdevice *s)
 256{
 257        struct adl_pci7x3x_dev_private_data *dev_private = dev->private;
 258        struct adl_pci7x3x_sd_private_data *sd_priv = s->private;
 259        unsigned long cpu_flags;
 260        unsigned int int_enab;
 261
 262        spin_lock_irqsave(&sd_priv->subd_slock, cpu_flags);
 263        sd_priv->cmd_running = 0;
 264        spin_unlock_irqrestore(&sd_priv->subd_slock, cpu_flags);
 265        /* disable Interrupts */
 266        if (s->index == 2)
 267                int_enab = PLX9052_INTCSR_LI1ENAB;
 268        else
 269                int_enab = PLX9052_INTCSR_LI2ENAB;
 270        spin_lock_irqsave(&dev->spinlock, cpu_flags);
 271        dev_private->int_ctrl &= ~int_enab;
 272        outl(dev_private->int_ctrl, dev_private->lcr_io_base + PLX9052_INTCSR);
 273        spin_unlock_irqrestore(&dev->spinlock, cpu_flags);
 274
 275        return 0;
 276}
 277
 278/* same as _di_insn_bits because the IRQ-pins are the DI-ports  */
 279static int adl_pci7x3x_dirq_insn_bits(struct comedi_device *dev,
 280                                      struct comedi_subdevice *s,
 281                                      struct comedi_insn *insn,
 282                                      unsigned int *data)
 283{
 284        struct adl_pci7x3x_sd_private_data *sd_priv = s->private;
 285        unsigned long reg = (unsigned long)sd_priv->port_offset;
 286
 287        data[1] = inl(dev->iobase + reg);
 288
 289        return insn->n;
 290}
 291
 292static int adl_pci7x3x_do_insn_bits(struct comedi_device *dev,
 293                                    struct comedi_subdevice *s,
 294                                    struct comedi_insn *insn,
 295                                    unsigned int *data)
 296{
 297        unsigned long reg = (unsigned long)s->private;
 298
 299        if (comedi_dio_update_state(s, data)) {
 300                unsigned int val = s->state;
 301
 302                if (s->n_chan == 16) {
 303                        /*
 304                         * It seems the PCI-7230 needs the 16-bit DO state
 305                         * to be shifted left by 16 bits before being written
 306                         * to the 32-bit register.  Set the value in both
 307                         * halves of the register to be sure.
 308                         */
 309                        val |= val << 16;
 310                }
 311                outl(val, dev->iobase + reg);
 312        }
 313
 314        data[1] = s->state;
 315
 316        return insn->n;
 317}
 318
 319static int adl_pci7x3x_di_insn_bits(struct comedi_device *dev,
 320                                    struct comedi_subdevice *s,
 321                                    struct comedi_insn *insn,
 322                                    unsigned int *data)
 323{
 324        unsigned long reg = (unsigned long)s->private;
 325
 326        data[1] = inl(dev->iobase + reg);
 327
 328        return insn->n;
 329}
 330
 331static int adl_pci7x3x_reset(struct comedi_device *dev)
 332{
 333        struct adl_pci7x3x_dev_private_data *dev_private = dev->private;
 334
 335        /* disable Interrupts */
 336        dev_private->int_ctrl = 0x00;  /* Disable PCI + LINTi2 + LINTi1 */
 337        outl(dev_private->int_ctrl, dev_private->lcr_io_base + PLX9052_INTCSR);
 338
 339        return 0;
 340}
 341
 342static int adl_pci7x3x_auto_attach(struct comedi_device *dev,
 343                                   unsigned long context)
 344{
 345        struct pci_dev *pcidev = comedi_to_pci_dev(dev);
 346        const struct adl_pci7x3x_boardinfo *board = NULL;
 347        struct comedi_subdevice *s;
 348        struct adl_pci7x3x_dev_private_data *dev_private;
 349        int subdev;
 350        int nchan;
 351        int ret;
 352        int ic;
 353
 354        if (context < ARRAY_SIZE(adl_pci7x3x_boards))
 355                board = &adl_pci7x3x_boards[context];
 356        if (!board)
 357                return -ENODEV;
 358        dev->board_ptr = board;
 359        dev->board_name = board->name;
 360
 361        dev_private = comedi_alloc_devpriv(dev, sizeof(*dev_private));
 362        if (!dev_private)
 363                return -ENOMEM;
 364
 365        ret = comedi_pci_enable(dev);
 366        if (ret)
 367                return ret;
 368        dev->iobase = pci_resource_start(pcidev, 2);
 369        dev_private->lcr_io_base = pci_resource_start(pcidev, 1);
 370
 371        adl_pci7x3x_reset(dev);
 372
 373        if (board->irq_nchan) {
 374                /* discard all evtl. old IRQs */
 375                outb(0x00, dev->iobase + ADL_PT_CLRIRQ);
 376
 377                if (pcidev->irq) {
 378                        ret = request_irq(pcidev->irq, adl_pci7x3x_interrupt,
 379                                          IRQF_SHARED, dev->board_name, dev);
 380                        if (ret == 0) {
 381                                dev->irq = pcidev->irq;
 382                                /* 0x52 PCI + IDI Ch 1 Ch 0 IRQ Off ActHigh */
 383                                dev_private->int_ctrl = EN_PCI_LINT2H_LINT1H;
 384                                outl(dev_private->int_ctrl,
 385                                     dev_private->lcr_io_base + PLX9052_INTCSR);
 386                        }
 387                }
 388        }
 389
 390        ret = comedi_alloc_subdevices(dev, board->nsubdevs);
 391        if (ret)
 392                return ret;
 393
 394        subdev = 0;
 395
 396        if (board->di_nchan) {
 397                nchan = min(board->di_nchan, 32);
 398
 399                s = &dev->subdevices[subdev];
 400                /* Isolated digital inputs 0 to 15/31 */
 401                s->type         = COMEDI_SUBD_DI;
 402                s->subdev_flags = SDF_READABLE;
 403                s->n_chan       = nchan;
 404                s->maxdata      = 1;
 405                s->insn_bits    = adl_pci7x3x_di_insn_bits;
 406                s->range_table  = &range_digital;
 407
 408                s->private      = (void *)PCI7X3X_DIO_REG;
 409
 410                subdev++;
 411
 412                nchan = board->di_nchan - nchan;
 413                if (nchan) {
 414                        s = &dev->subdevices[subdev];
 415                        /* Isolated digital inputs 32 to 63 */
 416                        s->type         = COMEDI_SUBD_DI;
 417                        s->subdev_flags = SDF_READABLE;
 418                        s->n_chan       = nchan;
 419                        s->maxdata      = 1;
 420                        s->insn_bits    = adl_pci7x3x_di_insn_bits;
 421                        s->range_table  = &range_digital;
 422
 423                        s->private      = (void *)PCI743X_DIO_REG;
 424
 425                        subdev++;
 426                }
 427        }
 428
 429        if (board->do_nchan) {
 430                nchan = min(board->do_nchan, 32);
 431
 432                s = &dev->subdevices[subdev];
 433                /* Isolated digital outputs 0 to 15/31 */
 434                s->type         = COMEDI_SUBD_DO;
 435                s->subdev_flags = SDF_WRITABLE;
 436                s->n_chan       = nchan;
 437                s->maxdata      = 1;
 438                s->insn_bits    = adl_pci7x3x_do_insn_bits;
 439                s->range_table  = &range_digital;
 440
 441                s->private      = (void *)PCI7X3X_DIO_REG;
 442
 443                subdev++;
 444
 445                nchan = board->do_nchan - nchan;
 446                if (nchan) {
 447                        s = &dev->subdevices[subdev];
 448                        /* Isolated digital outputs 32 to 63 */
 449                        s->type         = COMEDI_SUBD_DO;
 450                        s->subdev_flags = SDF_WRITABLE;
 451                        s->n_chan       = nchan;
 452                        s->maxdata      = 1;
 453                        s->insn_bits    = adl_pci7x3x_do_insn_bits;
 454                        s->range_table  = &range_digital;
 455
 456                        s->private      = (void *)PCI743X_DIO_REG;
 457
 458                        subdev++;
 459                }
 460        }
 461
 462        for (ic = 0; ic < board->irq_nchan; ++ic) {
 463                struct adl_pci7x3x_sd_private_data *sd_priv;
 464
 465                nchan = 1;
 466
 467                s = &dev->subdevices[subdev];
 468                /* Isolated digital inputs 0 or 1 */
 469                s->type         = COMEDI_SUBD_DI;
 470                s->subdev_flags = SDF_READABLE;
 471                s->n_chan       = nchan;
 472                s->maxdata      = 1;
 473                s->insn_bits    = adl_pci7x3x_dirq_insn_bits;
 474                s->range_table  = &range_digital;
 475
 476                sd_priv = comedi_alloc_spriv(s, sizeof(*sd_priv));
 477                if (!sd_priv)
 478                        return -ENOMEM;
 479
 480                spin_lock_init(&sd_priv->subd_slock);
 481                sd_priv->port_offset = PCI7X3X_DIO_REG;
 482                sd_priv->cmd_running = 0;
 483
 484                if (dev->irq) {
 485                        dev->read_subdev = s;
 486                        s->type         = COMEDI_SUBD_DI;
 487                        s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
 488                        s->len_chanlist = 1;
 489                        s->do_cmdtest   = adl_pci7x3x_asy_cmdtest;
 490                        s->do_cmd       = adl_pci7x3x_asy_cmd;
 491                        s->cancel       = adl_pci7x3x_asy_cancel;
 492                }
 493
 494                subdev++;
 495        }
 496
 497        return 0;
 498}
 499
 500static void adl_pci7x3x_detach(struct comedi_device *dev)
 501{
 502        if (dev->iobase)
 503                adl_pci7x3x_reset(dev);
 504        comedi_pci_detach(dev);
 505}
 506
 507static struct comedi_driver adl_pci7x3x_driver = {
 508        .driver_name    = "adl_pci7x3x",
 509        .module         = THIS_MODULE,
 510        .auto_attach    = adl_pci7x3x_auto_attach,
 511        .detach         = adl_pci7x3x_detach,
 512};
 513
 514static int adl_pci7x3x_pci_probe(struct pci_dev *dev,
 515                                 const struct pci_device_id *id)
 516{
 517        return comedi_pci_auto_config(dev, &adl_pci7x3x_driver,
 518                                      id->driver_data);
 519}
 520
 521static const struct pci_device_id adl_pci7x3x_pci_table[] = {
 522        { PCI_VDEVICE(ADLINK, 0x7230), BOARD_PCI7230 },
 523        { PCI_VDEVICE(ADLINK, 0x7233), BOARD_PCI7233 },
 524        { PCI_VDEVICE(ADLINK, 0x7234), BOARD_PCI7234 },
 525        { PCI_VDEVICE(ADLINK, 0x7432), BOARD_PCI7432 },
 526        { PCI_VDEVICE(ADLINK, 0x7433), BOARD_PCI7433 },
 527        { PCI_VDEVICE(ADLINK, 0x7434), BOARD_PCI7434 },
 528        { 0 }
 529};
 530MODULE_DEVICE_TABLE(pci, adl_pci7x3x_pci_table);
 531
 532static struct pci_driver adl_pci7x3x_pci_driver = {
 533        .name           = "adl_pci7x3x",
 534        .id_table       = adl_pci7x3x_pci_table,
 535        .probe          = adl_pci7x3x_pci_probe,
 536        .remove         = comedi_pci_auto_unconfig,
 537};
 538module_comedi_pci_driver(adl_pci7x3x_driver, adl_pci7x3x_pci_driver);
 539
 540MODULE_DESCRIPTION("ADLINK PCI-723x/743x Isolated Digital I/O boards");
 541MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
 542MODULE_LICENSE("GPL");
 543