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