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