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