1/* 2 * pcl711.c 3 * Comedi driver for PC-LabCard PCL-711 and AdSys ACL-8112 and compatibles 4 * Copyright (C) 1998 David A. Schleef <ds@schleef.org> 5 * Janne Jalkanen <jalkanen@cs.hut.fi> 6 * Eric Bunn <ebu@cs.hut.fi> 7 * 8 * COMEDI - Linux Control and Measurement Device Interface 9 * Copyright (C) 1998 David A. Schleef <ds@schleef.org> 10 * 11 * This program is free software; you can redistribute it and/or modify 12 * it under the terms of the GNU General Public License as published by 13 * the Free Software Foundation; either version 2 of the License, or 14 * (at your option) any later version. 15 * 16 * This program is distributed in the hope that it will be useful, 17 * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 * GNU General Public License for more details. 20 */ 21 22/* 23 * Driver: pcl711 24 * Description: Advantech PCL-711 and 711b, ADLink ACL-8112 25 * Devices: (Advantech) PCL-711 [pcl711] 26 * (Advantech) PCL-711B [pcl711b] 27 * (AdLink) ACL-8112HG [acl8112hg] 28 * (AdLink) ACL-8112DG [acl8112dg] 29 * Author: David A. Schleef <ds@schleef.org> 30 * Janne Jalkanen <jalkanen@cs.hut.fi> 31 * Eric Bunn <ebu@cs.hut.fi> 32 * Updated: 33 * Status: mostly complete 34 * 35 * Configuration Options: 36 * [0] - I/O port base 37 * [1] - IRQ, optional 38 */ 39 40#include <linux/module.h> 41#include <linux/delay.h> 42#include <linux/interrupt.h> 43 44#include "../comedidev.h" 45 46#include "comedi_fc.h" 47#include "8253.h" 48 49/* 50 * I/O port register map 51 */ 52#define PCL711_TIMER_BASE 0x00 53#define PCL711_AI_LSB_REG 0x04 54#define PCL711_AI_MSB_REG 0x05 55#define PCL711_AI_MSB_DRDY (1 << 4) 56#define PCL711_AO_LSB_REG(x) (0x04 + ((x) * 2)) 57#define PCL711_AO_MSB_REG(x) (0x05 + ((x) * 2)) 58#define PCL711_DI_LSB_REG 0x06 59#define PCL711_DI_MSB_REG 0x07 60#define PCL711_INT_STAT_REG 0x08 61#define PCL711_INT_STAT_CLR (0 << 0) /* any value will work */ 62#define PCL711_AI_GAIN_REG 0x09 63#define PCL711_AI_GAIN(x) (((x) & 0xf) << 0) 64#define PCL711_MUX_REG 0x0a 65#define PCL711_MUX_CHAN(x) (((x) & 0xf) << 0) 66#define PCL711_MUX_CS0 (1 << 4) 67#define PCL711_MUX_CS1 (1 << 5) 68#define PCL711_MUX_DIFF (PCL711_MUX_CS0 | PCL711_MUX_CS1) 69#define PCL711_MODE_REG 0x0b 70#define PCL711_MODE_DEFAULT (0 << 0) 71#define PCL711_MODE_SOFTTRIG (1 << 0) 72#define PCL711_MODE_EXT (2 << 0) 73#define PCL711_MODE_EXT_IRQ (3 << 0) 74#define PCL711_MODE_PACER (4 << 0) 75#define PCL711_MODE_PACER_IRQ (6 << 0) 76#define PCL711_MODE_IRQ(x) (((x) & 0x7) << 4) 77#define PCL711_SOFTTRIG_REG 0x0c 78#define PCL711_SOFTTRIG (0 << 0) /* any value will work */ 79#define PCL711_DO_LSB_REG 0x0d 80#define PCL711_DO_MSB_REG 0x0e 81 82static const struct comedi_lrange range_pcl711b_ai = { 83 5, { 84 BIP_RANGE(5), 85 BIP_RANGE(2.5), 86 BIP_RANGE(1.25), 87 BIP_RANGE(0.625), 88 BIP_RANGE(0.3125) 89 } 90}; 91 92static const struct comedi_lrange range_acl8112hg_ai = { 93 12, { 94 BIP_RANGE(5), 95 BIP_RANGE(0.5), 96 BIP_RANGE(0.05), 97 BIP_RANGE(0.005), 98 UNI_RANGE(10), 99 UNI_RANGE(1), 100 UNI_RANGE(0.1), 101 UNI_RANGE(0.01), 102 BIP_RANGE(10), 103 BIP_RANGE(1), 104 BIP_RANGE(0.1), 105 BIP_RANGE(0.01) 106 } 107}; 108 109static const struct comedi_lrange range_acl8112dg_ai = { 110 9, { 111 BIP_RANGE(5), 112 BIP_RANGE(2.5), 113 BIP_RANGE(1.25), 114 BIP_RANGE(0.625), 115 UNI_RANGE(10), 116 UNI_RANGE(5), 117 UNI_RANGE(2.5), 118 UNI_RANGE(1.25), 119 BIP_RANGE(10) 120 } 121}; 122 123struct pcl711_board { 124 const char *name; 125 int n_aichan; 126 int n_aochan; 127 int maxirq; 128 const struct comedi_lrange *ai_range_type; 129}; 130 131static const struct pcl711_board boardtypes[] = { 132 { 133 .name = "pcl711", 134 .n_aichan = 8, 135 .n_aochan = 1, 136 .ai_range_type = &range_bipolar5, 137 }, { 138 .name = "pcl711b", 139 .n_aichan = 8, 140 .n_aochan = 1, 141 .maxirq = 7, 142 .ai_range_type = &range_pcl711b_ai, 143 }, { 144 .name = "acl8112hg", 145 .n_aichan = 16, 146 .n_aochan = 2, 147 .maxirq = 15, 148 .ai_range_type = &range_acl8112hg_ai, 149 }, { 150 .name = "acl8112dg", 151 .n_aichan = 16, 152 .n_aochan = 2, 153 .maxirq = 15, 154 .ai_range_type = &range_acl8112dg_ai, 155 }, 156}; 157 158struct pcl711_private { 159 unsigned int ntrig; 160 unsigned int divisor1; 161 unsigned int divisor2; 162}; 163 164static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode) 165{ 166 /* 167 * The pcl711b board uses bits in the mode register to select the 168 * interrupt. The other boards supported by this driver all use 169 * jumpers on the board. 170 * 171 * Enables the interrupt when needed on the pcl711b board. These 172 * bits do nothing on the other boards. 173 */ 174 if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ) 175 mode |= PCL711_MODE_IRQ(dev->irq); 176 177 outb(mode, dev->iobase + PCL711_MODE_REG); 178} 179 180static unsigned int pcl711_ai_get_sample(struct comedi_device *dev, 181 struct comedi_subdevice *s) 182{ 183 unsigned int val; 184 185 val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8; 186 val |= inb(dev->iobase + PCL711_AI_LSB_REG); 187 188 return val & s->maxdata; 189} 190 191static int pcl711_ai_cancel(struct comedi_device *dev, 192 struct comedi_subdevice *s) 193{ 194 outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); 195 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG); 196 return 0; 197} 198 199static irqreturn_t pcl711_interrupt(int irq, void *d) 200{ 201 struct comedi_device *dev = d; 202 struct pcl711_private *devpriv = dev->private; 203 struct comedi_subdevice *s = dev->read_subdev; 204 struct comedi_cmd *cmd = &s->async->cmd; 205 unsigned int data; 206 207 if (!dev->attached) { 208 dev_err(dev->class_dev, "spurious interrupt\n"); 209 return IRQ_HANDLED; 210 } 211 212 data = pcl711_ai_get_sample(dev, s); 213 214 outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); 215 216 if (comedi_buf_put(s, data) == 0) { 217 s->async->events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR; 218 } else { 219 s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS; 220 if (cmd->stop_src == TRIG_COUNT && !(--devpriv->ntrig)) { 221 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG); 222 s->async->events |= COMEDI_CB_EOA; 223 } 224 } 225 comedi_event(dev, s); 226 return IRQ_HANDLED; 227} 228 229static void pcl711_set_changain(struct comedi_device *dev, 230 struct comedi_subdevice *s, 231 unsigned int chanspec) 232{ 233 unsigned int chan = CR_CHAN(chanspec); 234 unsigned int range = CR_RANGE(chanspec); 235 unsigned int aref = CR_AREF(chanspec); 236 unsigned int mux = 0; 237 238 outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG); 239 240 if (s->n_chan > 8) { 241 /* Select the correct MPC508A chip */ 242 if (aref == AREF_DIFF) { 243 chan &= 0x7; 244 mux |= PCL711_MUX_DIFF; 245 } else { 246 if (chan < 8) 247 mux |= PCL711_MUX_CS0; 248 else 249 mux |= PCL711_MUX_CS1; 250 } 251 } 252 outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG); 253} 254 255static int pcl711_ai_eoc(struct comedi_device *dev, 256 struct comedi_subdevice *s, 257 struct comedi_insn *insn, 258 unsigned long context) 259{ 260 unsigned int status; 261 262 status = inb(dev->iobase + PCL711_AI_MSB_REG); 263 if ((status & PCL711_AI_MSB_DRDY) == 0) 264 return 0; 265 return -EBUSY; 266} 267 268static int pcl711_ai_insn_read(struct comedi_device *dev, 269 struct comedi_subdevice *s, 270 struct comedi_insn *insn, 271 unsigned int *data) 272{ 273 int ret; 274 int i; 275 276 pcl711_set_changain(dev, s, insn->chanspec); 277 278 pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG); 279 280 for (i = 0; i < insn->n; i++) { 281 outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG); 282 283 ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0); 284 if (ret) 285 return ret; 286 287 data[i] = pcl711_ai_get_sample(dev, s); 288 } 289 290 return insn->n; 291} 292 293static int pcl711_ai_cmdtest(struct comedi_device *dev, 294 struct comedi_subdevice *s, struct comedi_cmd *cmd) 295{ 296 struct pcl711_private *devpriv = dev->private; 297 int err = 0; 298 unsigned int arg; 299 300 /* Step 1 : check if triggers are trivially valid */ 301 302 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW); 303 err |= cfc_check_trigger_src(&cmd->scan_begin_src, 304 TRIG_TIMER | TRIG_EXT); 305 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW); 306 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT); 307 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE); 308 309 if (err) 310 return 1; 311 312 /* Step 2a : make sure trigger sources are unique */ 313 314 err |= cfc_check_trigger_is_unique(cmd->scan_begin_src); 315 err |= cfc_check_trigger_is_unique(cmd->stop_src); 316 317 /* Step 2b : and mutually compatible */ 318 319 if (err) 320 return 2; 321 322 /* Step 3: check if arguments are trivially valid */ 323 324 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0); 325 326 if (cmd->scan_begin_src == TRIG_EXT) { 327 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0); 328 } else { 329#define MAX_SPEED 1000 330 err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg, 331 MAX_SPEED); 332 } 333 334 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0); 335 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len); 336 337 if (cmd->stop_src == TRIG_COUNT) 338 err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1); 339 else /* TRIG_NONE */ 340 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0); 341 342 if (err) 343 return 3; 344 345 /* step 4 */ 346 347 if (cmd->scan_begin_src == TRIG_TIMER) { 348 arg = cmd->scan_begin_arg; 349 i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ, 350 &devpriv->divisor1, 351 &devpriv->divisor2, 352 &arg, cmd->flags); 353 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg); 354 } 355 356 if (err) 357 return 4; 358 359 return 0; 360} 361 362static void pcl711_ai_load_counters(struct comedi_device *dev) 363{ 364 struct pcl711_private *devpriv = dev->private; 365 unsigned long timer_base = dev->iobase + PCL711_TIMER_BASE; 366 367 i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY); 368 i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY); 369 370 i8254_write(timer_base, 0, 1, devpriv->divisor1); 371 i8254_write(timer_base, 0, 2, devpriv->divisor2); 372} 373 374static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s) 375{ 376 struct pcl711_private *devpriv = dev->private; 377 struct comedi_cmd *cmd = &s->async->cmd; 378 379 pcl711_set_changain(dev, s, cmd->chanlist[0]); 380 381 if (cmd->stop_src == TRIG_COUNT) 382 devpriv->ntrig = cmd->stop_arg; 383 384 if (cmd->scan_begin_src == TRIG_TIMER) { 385 pcl711_ai_load_counters(dev); 386 outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG); 387 pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ); 388 } else { 389 pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ); 390 } 391 392 return 0; 393} 394 395static void pcl711_ao_write(struct comedi_device *dev, 396 unsigned int chan, unsigned int val) 397{ 398 outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan)); 399 outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan)); 400} 401 402static int pcl711_ao_insn_write(struct comedi_device *dev, 403 struct comedi_subdevice *s, 404 struct comedi_insn *insn, 405 unsigned int *data) 406{ 407 unsigned int chan = CR_CHAN(insn->chanspec); 408 unsigned int val = s->readback[chan]; 409 int i; 410 411 for (i = 0; i < insn->n; i++) { 412 val = data[i]; 413 pcl711_ao_write(dev, chan, val); 414 } 415 s->readback[chan] = val; 416 417 return insn->n; 418} 419 420static int pcl711_di_insn_bits(struct comedi_device *dev, 421 struct comedi_subdevice *s, 422 struct comedi_insn *insn, 423 unsigned int *data) 424{ 425 unsigned int val; 426 427 val = inb(dev->iobase + PCL711_DI_LSB_REG); 428 val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8); 429 430 data[1] = val; 431 432 return insn->n; 433} 434 435static int pcl711_do_insn_bits(struct comedi_device *dev, 436 struct comedi_subdevice *s, 437 struct comedi_insn *insn, 438 unsigned int *data) 439{ 440 unsigned int mask; 441 442 mask = comedi_dio_update_state(s, data); 443 if (mask) { 444 if (mask & 0x00ff) 445 outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG); 446 if (mask & 0xff00) 447 outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG); 448 } 449 450 data[1] = s->state; 451 452 return insn->n; 453} 454 455static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it) 456{ 457 const struct pcl711_board *board = dev->board_ptr; 458 struct pcl711_private *devpriv; 459 struct comedi_subdevice *s; 460 int ret; 461 462 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); 463 if (!devpriv) 464 return -ENOMEM; 465 466 ret = comedi_request_region(dev, it->options[0], 0x10); 467 if (ret) 468 return ret; 469 470 if (it->options[1] && it->options[1] <= board->maxirq) { 471 ret = request_irq(it->options[1], pcl711_interrupt, 0, 472 dev->board_name, dev); 473 if (ret == 0) 474 dev->irq = it->options[1]; 475 } 476 477 ret = comedi_alloc_subdevices(dev, 4); 478 if (ret) 479 return ret; 480 481 /* Analog Input subdevice */ 482 s = &dev->subdevices[0]; 483 s->type = COMEDI_SUBD_AI; 484 s->subdev_flags = SDF_READABLE | SDF_GROUND; 485 if (board->n_aichan > 8) 486 s->subdev_flags |= SDF_DIFF; 487 s->n_chan = board->n_aichan; 488 s->maxdata = 0xfff; 489 s->range_table = board->ai_range_type; 490 s->insn_read = pcl711_ai_insn_read; 491 if (dev->irq) { 492 dev->read_subdev = s; 493 s->subdev_flags |= SDF_CMD_READ; 494 s->len_chanlist = 1; 495 s->do_cmdtest = pcl711_ai_cmdtest; 496 s->do_cmd = pcl711_ai_cmd; 497 s->cancel = pcl711_ai_cancel; 498 } 499 500 /* Analog Output subdevice */ 501 s = &dev->subdevices[1]; 502 s->type = COMEDI_SUBD_AO; 503 s->subdev_flags = SDF_WRITABLE; 504 s->n_chan = board->n_aochan; 505 s->maxdata = 0xfff; 506 s->range_table = &range_bipolar5; 507 s->insn_write = pcl711_ao_insn_write; 508 s->insn_read = comedi_readback_insn_read; 509 510 ret = comedi_alloc_subdev_readback(s); 511 if (ret) 512 return ret; 513 514 /* Digital Input subdevice */ 515 s = &dev->subdevices[2]; 516 s->type = COMEDI_SUBD_DI; 517 s->subdev_flags = SDF_READABLE; 518 s->n_chan = 16; 519 s->maxdata = 1; 520 s->range_table = &range_digital; 521 s->insn_bits = pcl711_di_insn_bits; 522 523 /* Digital Output subdevice */ 524 s = &dev->subdevices[3]; 525 s->type = COMEDI_SUBD_DO; 526 s->subdev_flags = SDF_WRITABLE; 527 s->n_chan = 16; 528 s->maxdata = 1; 529 s->range_table = &range_digital; 530 s->insn_bits = pcl711_do_insn_bits; 531 532 /* clear DAC */ 533 pcl711_ao_write(dev, 0, 0x0); 534 pcl711_ao_write(dev, 1, 0x0); 535 536 return 0; 537} 538 539static struct comedi_driver pcl711_driver = { 540 .driver_name = "pcl711", 541 .module = THIS_MODULE, 542 .attach = pcl711_attach, 543 .detach = comedi_legacy_detach, 544 .board_name = &boardtypes[0].name, 545 .num_names = ARRAY_SIZE(boardtypes), 546 .offset = sizeof(struct pcl711_board), 547}; 548module_comedi_driver(pcl711_driver); 549 550MODULE_AUTHOR("Comedi http://www.comedi.org"); 551MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards"); 552MODULE_LICENSE("GPL"); 553