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