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