1// SPDX-License-Identifier: GPL-2.0 2/* 3 * comedi/drivers/pcl730.c 4 * Driver for Advantech PCL-730 and clones 5 * José Luis Sánchez 6 */ 7 8/* 9 * Driver: pcl730 10 * Description: Advantech PCL-730 (& compatibles) 11 * Devices: [Advantech] PCL-730 (pcl730), PCM-3730 (pcm3730), PCL-725 (pcl725), 12 * PCL-733 (pcl733), PCL-734 (pcl734), 13 * [ADLink] ACL-7130 (acl7130), ACL-7225b (acl7225b), 14 * [ICP] ISO-730 (iso730), P8R8-DIO (p8r8dio), P16R16-DIO (p16r16dio), 15 * [Diamond Systems] OPMM-1616-XT (opmm-1616-xt), PEARL-MM-P (pearl-mm-p), 16 * IR104-PBF (ir104-pbf), 17 * Author: José Luis Sánchez (jsanchezv@teleline.es) 18 * Status: untested 19 * 20 * Configuration options: 21 * [0] - I/O port base 22 * 23 * Interrupts are not supported. 24 * The ACL-7130 card has an 8254 timer/counter not supported by this driver. 25 */ 26 27#include <linux/module.h> 28#include <linux/comedi/comedidev.h> 29 30/* 31 * Register map 32 * 33 * The register map varies slightly depending on the board type but 34 * all registers are 8-bit. 35 * 36 * The boardinfo 'io_range' is used to allow comedi to request the 37 * proper range required by the board. 38 * 39 * The comedi_subdevice 'private' data is used to pass the register 40 * offset to the (*insn_bits) functions to read/write the correct 41 * registers. 42 * 43 * The basic register mapping looks like this: 44 * 45 * BASE+0 Isolated outputs 0-7 (write) / inputs 0-7 (read) 46 * BASE+1 Isolated outputs 8-15 (write) / inputs 8-15 (read) 47 * BASE+2 TTL outputs 0-7 (write) / inputs 0-7 (read) 48 * BASE+3 TTL outputs 8-15 (write) / inputs 8-15 (read) 49 * 50 * The pcm3730 board does not have register BASE+1. 51 * 52 * The pcl725 and p8r8dio only have registers BASE+0 and BASE+1: 53 * 54 * BASE+0 Isolated outputs 0-7 (write) (read back on p8r8dio) 55 * BASE+1 Isolated inputs 0-7 (read) 56 * 57 * The acl7225b and p16r16dio boards have this register mapping: 58 * 59 * BASE+0 Isolated outputs 0-7 (write) (read back) 60 * BASE+1 Isolated outputs 8-15 (write) (read back) 61 * BASE+2 Isolated inputs 0-7 (read) 62 * BASE+3 Isolated inputs 8-15 (read) 63 * 64 * The pcl733 and pcl733 boards have this register mapping: 65 * 66 * BASE+0 Isolated outputs 0-7 (write) or inputs 0-7 (read) 67 * BASE+1 Isolated outputs 8-15 (write) or inputs 8-15 (read) 68 * BASE+2 Isolated outputs 16-23 (write) or inputs 16-23 (read) 69 * BASE+3 Isolated outputs 24-31 (write) or inputs 24-31 (read) 70 * 71 * The opmm-1616-xt board has this register mapping: 72 * 73 * BASE+0 Isolated outputs 0-7 (write) (read back) 74 * BASE+1 Isolated outputs 8-15 (write) (read back) 75 * BASE+2 Isolated inputs 0-7 (read) 76 * BASE+3 Isolated inputs 8-15 (read) 77 * 78 * These registers are not currently supported: 79 * 80 * BASE+2 Relay select register (write) 81 * BASE+3 Board reset control register (write) 82 * BASE+4 Interrupt control register (write) 83 * BASE+4 Change detect 7-0 status register (read) 84 * BASE+5 LED control register (write) 85 * BASE+5 Change detect 15-8 status register (read) 86 * 87 * The pearl-mm-p board has this register mapping: 88 * 89 * BASE+0 Isolated outputs 0-7 (write) 90 * BASE+1 Isolated outputs 8-15 (write) 91 * 92 * The ir104-pbf board has this register mapping: 93 * 94 * BASE+0 Isolated outputs 0-7 (write) (read back) 95 * BASE+1 Isolated outputs 8-15 (write) (read back) 96 * BASE+2 Isolated outputs 16-19 (write) (read back) 97 * BASE+4 Isolated inputs 0-7 (read) 98 * BASE+5 Isolated inputs 8-15 (read) 99 * BASE+6 Isolated inputs 16-19 (read) 100 */ 101 102struct pcl730_board { 103 const char *name; 104 unsigned int io_range; 105 unsigned is_pcl725:1; 106 unsigned is_acl7225b:1; 107 unsigned is_ir104:1; 108 unsigned has_readback:1; 109 unsigned has_ttl_io:1; 110 int n_subdevs; 111 int n_iso_out_chan; 112 int n_iso_in_chan; 113 int n_ttl_chan; 114}; 115 116static const struct pcl730_board pcl730_boards[] = { 117 { 118 .name = "pcl730", 119 .io_range = 0x04, 120 .has_ttl_io = 1, 121 .n_subdevs = 4, 122 .n_iso_out_chan = 16, 123 .n_iso_in_chan = 16, 124 .n_ttl_chan = 16, 125 }, { 126 .name = "iso730", 127 .io_range = 0x04, 128 .n_subdevs = 4, 129 .n_iso_out_chan = 16, 130 .n_iso_in_chan = 16, 131 .n_ttl_chan = 16, 132 }, { 133 .name = "acl7130", 134 .io_range = 0x08, 135 .has_ttl_io = 1, 136 .n_subdevs = 4, 137 .n_iso_out_chan = 16, 138 .n_iso_in_chan = 16, 139 .n_ttl_chan = 16, 140 }, { 141 .name = "pcm3730", 142 .io_range = 0x04, 143 .has_ttl_io = 1, 144 .n_subdevs = 4, 145 .n_iso_out_chan = 8, 146 .n_iso_in_chan = 8, 147 .n_ttl_chan = 16, 148 }, { 149 .name = "pcl725", 150 .io_range = 0x02, 151 .is_pcl725 = 1, 152 .n_subdevs = 2, 153 .n_iso_out_chan = 8, 154 .n_iso_in_chan = 8, 155 }, { 156 .name = "p8r8dio", 157 .io_range = 0x02, 158 .is_pcl725 = 1, 159 .has_readback = 1, 160 .n_subdevs = 2, 161 .n_iso_out_chan = 8, 162 .n_iso_in_chan = 8, 163 }, { 164 .name = "acl7225b", 165 .io_range = 0x08, /* only 4 are used */ 166 .is_acl7225b = 1, 167 .has_readback = 1, 168 .n_subdevs = 2, 169 .n_iso_out_chan = 16, 170 .n_iso_in_chan = 16, 171 }, { 172 .name = "p16r16dio", 173 .io_range = 0x04, 174 .is_acl7225b = 1, 175 .has_readback = 1, 176 .n_subdevs = 2, 177 .n_iso_out_chan = 16, 178 .n_iso_in_chan = 16, 179 }, { 180 .name = "pcl733", 181 .io_range = 0x04, 182 .n_subdevs = 1, 183 .n_iso_in_chan = 32, 184 }, { 185 .name = "pcl734", 186 .io_range = 0x04, 187 .n_subdevs = 1, 188 .n_iso_out_chan = 32, 189 }, { 190 .name = "opmm-1616-xt", 191 .io_range = 0x10, 192 .is_acl7225b = 1, 193 .has_readback = 1, 194 .n_subdevs = 2, 195 .n_iso_out_chan = 16, 196 .n_iso_in_chan = 16, 197 }, { 198 .name = "pearl-mm-p", 199 .io_range = 0x02, 200 .n_subdevs = 1, 201 .n_iso_out_chan = 16, 202 }, { 203 .name = "ir104-pbf", 204 .io_range = 0x08, 205 .is_ir104 = 1, 206 .has_readback = 1, 207 .n_iso_out_chan = 20, 208 .n_iso_in_chan = 20, 209 }, 210}; 211 212static int pcl730_do_insn_bits(struct comedi_device *dev, 213 struct comedi_subdevice *s, 214 struct comedi_insn *insn, 215 unsigned int *data) 216{ 217 unsigned long reg = (unsigned long)s->private; 218 unsigned int mask; 219 220 mask = comedi_dio_update_state(s, data); 221 if (mask) { 222 if (mask & 0x00ff) 223 outb(s->state & 0xff, dev->iobase + reg); 224 if ((mask & 0xff00) && (s->n_chan > 8)) 225 outb((s->state >> 8) & 0xff, dev->iobase + reg + 1); 226 if ((mask & 0xff0000) && (s->n_chan > 16)) 227 outb((s->state >> 16) & 0xff, dev->iobase + reg + 2); 228 if ((mask & 0xff000000) && (s->n_chan > 24)) 229 outb((s->state >> 24) & 0xff, dev->iobase + reg + 3); 230 } 231 232 data[1] = s->state; 233 234 return insn->n; 235} 236 237static unsigned int pcl730_get_bits(struct comedi_device *dev, 238 struct comedi_subdevice *s) 239{ 240 unsigned long reg = (unsigned long)s->private; 241 unsigned int val; 242 243 val = inb(dev->iobase + reg); 244 if (s->n_chan > 8) 245 val |= (inb(dev->iobase + reg + 1) << 8); 246 if (s->n_chan > 16) 247 val |= (inb(dev->iobase + reg + 2) << 16); 248 if (s->n_chan > 24) 249 val |= (inb(dev->iobase + reg + 3) << 24); 250 251 return val; 252} 253 254static int pcl730_di_insn_bits(struct comedi_device *dev, 255 struct comedi_subdevice *s, 256 struct comedi_insn *insn, 257 unsigned int *data) 258{ 259 data[1] = pcl730_get_bits(dev, s); 260 261 return insn->n; 262} 263 264static int pcl730_attach(struct comedi_device *dev, 265 struct comedi_devconfig *it) 266{ 267 const struct pcl730_board *board = dev->board_ptr; 268 struct comedi_subdevice *s; 269 int subdev; 270 int ret; 271 272 ret = comedi_request_region(dev, it->options[0], board->io_range); 273 if (ret) 274 return ret; 275 276 ret = comedi_alloc_subdevices(dev, board->n_subdevs); 277 if (ret) 278 return ret; 279 280 subdev = 0; 281 282 if (board->n_iso_out_chan) { 283 /* Isolated Digital Outputs */ 284 s = &dev->subdevices[subdev++]; 285 s->type = COMEDI_SUBD_DO; 286 s->subdev_flags = SDF_WRITABLE; 287 s->n_chan = board->n_iso_out_chan; 288 s->maxdata = 1; 289 s->range_table = &range_digital; 290 s->insn_bits = pcl730_do_insn_bits; 291 s->private = (void *)0; 292 293 /* get the initial state if supported */ 294 if (board->has_readback) 295 s->state = pcl730_get_bits(dev, s); 296 } 297 298 if (board->n_iso_in_chan) { 299 /* Isolated Digital Inputs */ 300 s = &dev->subdevices[subdev++]; 301 s->type = COMEDI_SUBD_DI; 302 s->subdev_flags = SDF_READABLE; 303 s->n_chan = board->n_iso_in_chan; 304 s->maxdata = 1; 305 s->range_table = &range_digital; 306 s->insn_bits = pcl730_di_insn_bits; 307 s->private = board->is_ir104 ? (void *)4 : 308 board->is_acl7225b ? (void *)2 : 309 board->is_pcl725 ? (void *)1 : (void *)0; 310 } 311 312 if (board->has_ttl_io) { 313 /* TTL Digital Outputs */ 314 s = &dev->subdevices[subdev++]; 315 s->type = COMEDI_SUBD_DO; 316 s->subdev_flags = SDF_WRITABLE; 317 s->n_chan = board->n_ttl_chan; 318 s->maxdata = 1; 319 s->range_table = &range_digital; 320 s->insn_bits = pcl730_do_insn_bits; 321 s->private = (void *)2; 322 323 /* TTL Digital Inputs */ 324 s = &dev->subdevices[subdev++]; 325 s->type = COMEDI_SUBD_DI; 326 s->subdev_flags = SDF_READABLE; 327 s->n_chan = board->n_ttl_chan; 328 s->maxdata = 1; 329 s->range_table = &range_digital; 330 s->insn_bits = pcl730_di_insn_bits; 331 s->private = (void *)2; 332 } 333 334 return 0; 335} 336 337static struct comedi_driver pcl730_driver = { 338 .driver_name = "pcl730", 339 .module = THIS_MODULE, 340 .attach = pcl730_attach, 341 .detach = comedi_legacy_detach, 342 .board_name = &pcl730_boards[0].name, 343 .num_names = ARRAY_SIZE(pcl730_boards), 344 .offset = sizeof(struct pcl730_board), 345}; 346module_comedi_driver(pcl730_driver); 347 348MODULE_AUTHOR("Comedi https://www.comedi.org"); 349MODULE_DESCRIPTION("Comedi low-level driver"); 350MODULE_LICENSE("GPL"); 351