linux/drivers/staging/comedi/drivers/pcl726.c
<<
>>
Prefs
   1/*
   2 * pcl726.c
   3 * Comedi driver for 6/12-Channel D/A Output and DIO cards
   4 *
   5 * COMEDI - Linux Control and Measurement Device Interface
   6 * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
   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: pcl726
  21 * Description: Advantech PCL-726 & compatibles
  22 * Author: David A. Schleef <ds@schleef.org>
  23 * Status: untested
  24 * Devices: [Advantech] PCL-726 (pcl726), PCL-727 (pcl727), PCL-728 (pcl728),
  25 *   [ADLink] ACL-6126 (acl6126), ACL-6128 (acl6128)
  26 *
  27 * Configuration Options:
  28 *   [0]  - IO Base
  29 *   [1]  - IRQ (ACL-6126 only)
  30 *   [2]  - D/A output range for channel 0
  31 *   [3]  - D/A output range for channel 1
  32 *
  33 * Boards with > 2 analog output channels:
  34 *   [4]  - D/A output range for channel 2
  35 *   [5]  - D/A output range for channel 3
  36 *   [6]  - D/A output range for channel 4
  37 *   [7]  - D/A output range for channel 5
  38 *
  39 * Boards with > 6 analog output channels:
  40 *   [8]  - D/A output range for channel 6
  41 *   [9]  - D/A output range for channel 7
  42 *   [10] - D/A output range for channel 8
  43 *   [11] - D/A output range for channel 9
  44 *   [12] - D/A output range for channel 10
  45 *   [13] - D/A output range for channel 11
  46 *
  47 * For PCL-726 the D/A output ranges are:
  48 *   0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: unknown
  49 *
  50 * For PCL-727:
  51 *   0: 0-5V, 1: 0-10V, 2: +/-5V, 3: 4-20mA
  52 *
  53 * For PCL-728 and ACL-6128:
  54 *   0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA, 5: 0-20mA
  55 *
  56 * For ACL-6126:
  57 *   0: 0-5V, 1: 0-10V, 2: +/-5V, 3: +/-10V, 4: 4-20mA
  58 */
  59
  60#include <linux/module.h>
  61#include <linux/interrupt.h>
  62
  63#include "../comedidev.h"
  64
  65#define PCL726_AO_MSB_REG(x)    (0x00 + ((x) * 2))
  66#define PCL726_AO_LSB_REG(x)    (0x01 + ((x) * 2))
  67#define PCL726_DO_MSB_REG       0x0c
  68#define PCL726_DO_LSB_REG       0x0d
  69#define PCL726_DI_MSB_REG       0x0e
  70#define PCL726_DI_LSB_REG       0x0f
  71
  72#define PCL727_DI_MSB_REG       0x00
  73#define PCL727_DI_LSB_REG       0x01
  74#define PCL727_DO_MSB_REG       0x18
  75#define PCL727_DO_LSB_REG       0x19
  76
  77static const struct comedi_lrange *const rangelist_726[] = {
  78        &range_unipolar5,
  79        &range_unipolar10,
  80        &range_bipolar5,
  81        &range_bipolar10,
  82        &range_4_20mA,
  83        &range_unknown
  84};
  85
  86static const struct comedi_lrange *const rangelist_727[] = {
  87        &range_unipolar5,
  88        &range_unipolar10,
  89        &range_bipolar5,
  90        &range_4_20mA
  91};
  92
  93static const struct comedi_lrange *const rangelist_728[] = {
  94        &range_unipolar5,
  95        &range_unipolar10,
  96        &range_bipolar5,
  97        &range_bipolar10,
  98        &range_4_20mA,
  99        &range_0_20mA
 100};
 101
 102struct pcl726_board {
 103        const char *name;
 104        unsigned long io_len;
 105        unsigned int irq_mask;
 106        const struct comedi_lrange *const *ao_ranges;
 107        int ao_num_ranges;
 108        int ao_nchan;
 109        unsigned int have_dio:1;
 110        unsigned int is_pcl727:1;
 111};
 112
 113static const struct pcl726_board pcl726_boards[] = {
 114        {
 115                .name           = "pcl726",
 116                .io_len         = 0x10,
 117                .ao_ranges      = &rangelist_726[0],
 118                .ao_num_ranges  = ARRAY_SIZE(rangelist_726),
 119                .ao_nchan       = 6,
 120                .have_dio       = 1,
 121        }, {
 122                .name           = "pcl727",
 123                .io_len         = 0x20,
 124                .ao_ranges      = &rangelist_727[0],
 125                .ao_num_ranges  = ARRAY_SIZE(rangelist_727),
 126                .ao_nchan       = 12,
 127                .have_dio       = 1,
 128                .is_pcl727      = 1,
 129        }, {
 130                .name           = "pcl728",
 131                .io_len         = 0x08,
 132                .ao_num_ranges  = ARRAY_SIZE(rangelist_728),
 133                .ao_ranges      = &rangelist_728[0],
 134                .ao_nchan       = 2,
 135        }, {
 136                .name           = "acl6126",
 137                .io_len         = 0x10,
 138                .irq_mask       = 0x96e8,
 139                .ao_num_ranges  = ARRAY_SIZE(rangelist_726),
 140                .ao_ranges      = &rangelist_726[0],
 141                .ao_nchan       = 6,
 142                .have_dio       = 1,
 143        }, {
 144                .name           = "acl6128",
 145                .io_len         = 0x08,
 146                .ao_num_ranges  = ARRAY_SIZE(rangelist_728),
 147                .ao_ranges      = &rangelist_728[0],
 148                .ao_nchan       = 2,
 149        },
 150};
 151
 152struct pcl726_private {
 153        const struct comedi_lrange *rangelist[12];
 154        unsigned int cmd_running:1;
 155};
 156
 157static int pcl726_intr_insn_bits(struct comedi_device *dev,
 158                                 struct comedi_subdevice *s,
 159                                 struct comedi_insn *insn,
 160                                 unsigned int *data)
 161{
 162        data[1] = 0;
 163        return insn->n;
 164}
 165
 166static int pcl726_intr_cmdtest(struct comedi_device *dev,
 167                               struct comedi_subdevice *s,
 168                               struct comedi_cmd *cmd)
 169{
 170        int err = 0;
 171
 172        /* Step 1 : check if triggers are trivially valid */
 173
 174        err |= comedi_check_trigger_src(&cmd->start_src, TRIG_NOW);
 175        err |= comedi_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
 176        err |= comedi_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
 177        err |= comedi_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
 178        err |= comedi_check_trigger_src(&cmd->stop_src, TRIG_NONE);
 179
 180        if (err)
 181                return 1;
 182
 183        /* Step 2a : make sure trigger sources are unique */
 184        /* Step 2b : and mutually compatible */
 185
 186        /* Step 3: check if arguments are trivially valid */
 187
 188        err |= comedi_check_trigger_arg_is(&cmd->start_arg, 0);
 189        err |= comedi_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
 190        err |= comedi_check_trigger_arg_is(&cmd->convert_arg, 0);
 191        err |= comedi_check_trigger_arg_is(&cmd->scan_end_arg,
 192                                           cmd->chanlist_len);
 193        err |= comedi_check_trigger_arg_is(&cmd->stop_arg, 0);
 194
 195        if (err)
 196                return 3;
 197
 198        /* Step 4: fix up any arguments */
 199
 200        /* Step 5: check channel list if it exists */
 201
 202        return 0;
 203}
 204
 205static int pcl726_intr_cmd(struct comedi_device *dev,
 206                           struct comedi_subdevice *s)
 207{
 208        struct pcl726_private *devpriv = dev->private;
 209
 210        devpriv->cmd_running = 1;
 211
 212        return 0;
 213}
 214
 215static int pcl726_intr_cancel(struct comedi_device *dev,
 216                              struct comedi_subdevice *s)
 217{
 218        struct pcl726_private *devpriv = dev->private;
 219
 220        devpriv->cmd_running = 0;
 221
 222        return 0;
 223}
 224
 225static irqreturn_t pcl726_interrupt(int irq, void *d)
 226{
 227        struct comedi_device *dev = d;
 228        struct comedi_subdevice *s = dev->read_subdev;
 229        struct pcl726_private *devpriv = dev->private;
 230
 231        if (devpriv->cmd_running) {
 232                pcl726_intr_cancel(dev, s);
 233
 234                comedi_buf_write_samples(s, &s->state, 1);
 235                comedi_handle_events(dev, s);
 236        }
 237
 238        return IRQ_HANDLED;
 239}
 240
 241static int pcl726_ao_insn_write(struct comedi_device *dev,
 242                                struct comedi_subdevice *s,
 243                                struct comedi_insn *insn,
 244                                unsigned int *data)
 245{
 246        unsigned int chan = CR_CHAN(insn->chanspec);
 247        unsigned int range = CR_RANGE(insn->chanspec);
 248        int i;
 249
 250        for (i = 0; i < insn->n; i++) {
 251                unsigned int val = data[i];
 252
 253                s->readback[chan] = val;
 254
 255                /* bipolar data to the DAC is two's complement */
 256                if (comedi_chan_range_is_bipolar(s, chan, range))
 257                        val = comedi_offset_munge(s, val);
 258
 259                /* order is important, MSB then LSB */
 260                outb((val >> 8) & 0xff, dev->iobase + PCL726_AO_MSB_REG(chan));
 261                outb(val & 0xff, dev->iobase + PCL726_AO_LSB_REG(chan));
 262        }
 263
 264        return insn->n;
 265}
 266
 267static int pcl726_di_insn_bits(struct comedi_device *dev,
 268                               struct comedi_subdevice *s,
 269                               struct comedi_insn *insn,
 270                               unsigned int *data)
 271{
 272        const struct pcl726_board *board = dev->board_ptr;
 273        unsigned int val;
 274
 275        if (board->is_pcl727) {
 276                val = inb(dev->iobase + PCL727_DI_LSB_REG);
 277                val |= (inb(dev->iobase + PCL727_DI_MSB_REG) << 8);
 278        } else {
 279                val = inb(dev->iobase + PCL726_DI_LSB_REG);
 280                val |= (inb(dev->iobase + PCL726_DI_MSB_REG) << 8);
 281        }
 282
 283        data[1] = val;
 284
 285        return insn->n;
 286}
 287
 288static int pcl726_do_insn_bits(struct comedi_device *dev,
 289                               struct comedi_subdevice *s,
 290                               struct comedi_insn *insn,
 291                               unsigned int *data)
 292{
 293        const struct pcl726_board *board = dev->board_ptr;
 294        unsigned long io = dev->iobase;
 295        unsigned int mask;
 296
 297        mask = comedi_dio_update_state(s, data);
 298        if (mask) {
 299                if (board->is_pcl727) {
 300                        if (mask & 0x00ff)
 301                                outb(s->state & 0xff, io + PCL727_DO_LSB_REG);
 302                        if (mask & 0xff00)
 303                                outb((s->state >> 8), io + PCL727_DO_MSB_REG);
 304                } else {
 305                        if (mask & 0x00ff)
 306                                outb(s->state & 0xff, io + PCL726_DO_LSB_REG);
 307                        if (mask & 0xff00)
 308                                outb((s->state >> 8), io + PCL726_DO_MSB_REG);
 309                }
 310        }
 311
 312        data[1] = s->state;
 313
 314        return insn->n;
 315}
 316
 317static int pcl726_attach(struct comedi_device *dev,
 318                         struct comedi_devconfig *it)
 319{
 320        const struct pcl726_board *board = dev->board_ptr;
 321        struct pcl726_private *devpriv;
 322        struct comedi_subdevice *s;
 323        int subdev;
 324        int ret;
 325        int i;
 326
 327        ret = comedi_request_region(dev, it->options[0], board->io_len);
 328        if (ret)
 329                return ret;
 330
 331        devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
 332        if (!devpriv)
 333                return -ENOMEM;
 334
 335        /*
 336         * Hook up the external trigger source interrupt only if the
 337         * user config option is valid and the board supports interrupts.
 338         */
 339        if (it->options[1] && (board->irq_mask & (1 << it->options[1]))) {
 340                ret = request_irq(it->options[1], pcl726_interrupt, 0,
 341                                  dev->board_name, dev);
 342                if (ret == 0) {
 343                        /* External trigger source is from Pin-17 of CN3 */
 344                        dev->irq = it->options[1];
 345                }
 346        }
 347
 348        /* setup the per-channel analog output range_table_list */
 349        for (i = 0; i < 12; i++) {
 350                unsigned int opt = it->options[2 + i];
 351
 352                if (opt < board->ao_num_ranges && i < board->ao_nchan)
 353                        devpriv->rangelist[i] = board->ao_ranges[opt];
 354                else
 355                        devpriv->rangelist[i] = &range_unknown;
 356        }
 357
 358        subdev = board->have_dio ? 3 : 1;
 359        if (dev->irq)
 360                subdev++;
 361        ret = comedi_alloc_subdevices(dev, subdev);
 362        if (ret)
 363                return ret;
 364
 365        subdev = 0;
 366
 367        /* Analog Output subdevice */
 368        s = &dev->subdevices[subdev++];
 369        s->type         = COMEDI_SUBD_AO;
 370        s->subdev_flags = SDF_WRITABLE | SDF_GROUND;
 371        s->n_chan       = board->ao_nchan;
 372        s->maxdata      = 0x0fff;
 373        s->range_table_list = devpriv->rangelist;
 374        s->insn_write   = pcl726_ao_insn_write;
 375
 376        ret = comedi_alloc_subdev_readback(s);
 377        if (ret)
 378                return ret;
 379
 380        if (board->have_dio) {
 381                /* Digital Input subdevice */
 382                s = &dev->subdevices[subdev++];
 383                s->type         = COMEDI_SUBD_DI;
 384                s->subdev_flags = SDF_READABLE;
 385                s->n_chan       = 16;
 386                s->maxdata      = 1;
 387                s->insn_bits    = pcl726_di_insn_bits;
 388                s->range_table  = &range_digital;
 389
 390                /* Digital Output subdevice */
 391                s = &dev->subdevices[subdev++];
 392                s->type         = COMEDI_SUBD_DO;
 393                s->subdev_flags = SDF_WRITABLE;
 394                s->n_chan       = 16;
 395                s->maxdata      = 1;
 396                s->insn_bits    = pcl726_do_insn_bits;
 397                s->range_table  = &range_digital;
 398        }
 399
 400        if (dev->irq) {
 401                /* Digial Input subdevice - Interrupt support */
 402                s = &dev->subdevices[subdev++];
 403                dev->read_subdev = s;
 404                s->type         = COMEDI_SUBD_DI;
 405                s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
 406                s->n_chan       = 1;
 407                s->maxdata      = 1;
 408                s->range_table  = &range_digital;
 409                s->insn_bits    = pcl726_intr_insn_bits;
 410                s->len_chanlist = 1;
 411                s->do_cmdtest   = pcl726_intr_cmdtest;
 412                s->do_cmd       = pcl726_intr_cmd;
 413                s->cancel       = pcl726_intr_cancel;
 414        }
 415
 416        return 0;
 417}
 418
 419static struct comedi_driver pcl726_driver = {
 420        .driver_name    = "pcl726",
 421        .module         = THIS_MODULE,
 422        .attach         = pcl726_attach,
 423        .detach         = comedi_legacy_detach,
 424        .board_name     = &pcl726_boards[0].name,
 425        .num_names      = ARRAY_SIZE(pcl726_boards),
 426        .offset         = sizeof(struct pcl726_board),
 427};
 428module_comedi_driver(pcl726_driver);
 429
 430MODULE_AUTHOR("Comedi http://www.comedi.org");
 431MODULE_DESCRIPTION("Comedi driver for Advantech PCL-726 & compatibles");
 432MODULE_LICENSE("GPL");
 433