1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * COMEDI driver for the Advantech PCI-1760 4 * Copyright (C) 2015 H Hartley Sweeten <hsweeten@visionengravers.com> 5 * 6 * Based on the pci1760 support in the adv_pci_dio driver written by: 7 * Michal Dobes <dobes@tesnet.cz> 8 * 9 * COMEDI - Linux Control and Measurement Device Interface 10 * Copyright (C) 2000 David A. Schleef <ds@schleef.org> 11 */ 12 13 /* 14 * Driver: adv_pci1760 15 * Description: Advantech PCI-1760 Relay & Isolated Digital Input Card 16 * Devices: [Advantech] PCI-1760 (adv_pci1760) 17 * Author: H Hartley Sweeten <hsweeten@visionengravers.com> 18 * Updated: Fri, 13 Nov 2015 12:34:00 -0700 19 * Status: untested 20 * 21 * Configuration Options: not applicable, uses PCI auto config 22 */ 23 24 #include <linux/module.h> 25 #include <linux/comedi/comedi_pci.h> 26 27 /* 28 * PCI-1760 Register Map 29 * 30 * Outgoing Mailbox Bytes 31 * OMB3: Not used (must be 0) 32 * OMB2: The command code to the PCI-1760 33 * OMB1: The hi byte of the parameter for the command in OMB2 34 * OMB0: The lo byte of the parameter for the command in OMB2 35 * 36 * Incoming Mailbox Bytes 37 * IMB3: The Isolated Digital Input status (updated every 100us) 38 * IMB2: The current command (matches OMB2 when command is successful) 39 * IMB1: The hi byte of the feedback data for the command in OMB2 40 * IMB0: The lo byte of the feedback data for the command in OMB2 41 * 42 * Interrupt Control/Status 43 * INTCSR3: Not used (must be 0) 44 * INTCSR2: The interrupt status (read only) 45 * INTCSR1: Interrupt enable/disable 46 * INTCSR0: Not used (must be 0) 47 */ 48 #define PCI1760_OMB_REG(x) (0x0c + (x)) 49 #define PCI1760_IMB_REG(x) (0x1c + (x)) 50 #define PCI1760_INTCSR_REG(x) (0x38 + (x)) 51 #define PCI1760_INTCSR1_IRQ_ENA BIT(5) 52 #define PCI1760_INTCSR2_OMB_IRQ BIT(0) 53 #define PCI1760_INTCSR2_IMB_IRQ BIT(1) 54 #define PCI1760_INTCSR2_IRQ_STATUS BIT(6) 55 #define PCI1760_INTCSR2_IRQ_ASSERTED BIT(7) 56 57 /* PCI-1760 command codes */ 58 #define PCI1760_CMD_CLR_IMB2 0x00 /* Clears IMB2 */ 59 #define PCI1760_CMD_SET_DO 0x01 /* Set output state */ 60 #define PCI1760_CMD_GET_DO 0x02 /* Read output status */ 61 #define PCI1760_CMD_GET_STATUS 0x07 /* Read current status */ 62 #define PCI1760_CMD_GET_FW_VER 0x0e /* Read firmware version */ 63 #define PCI1760_CMD_GET_HW_VER 0x0f /* Read hardware version */ 64 #define PCI1760_CMD_SET_PWM_HI(x) (0x10 + (x) * 2) /* Set "hi" period */ 65 #define PCI1760_CMD_SET_PWM_LO(x) (0x11 + (x) * 2) /* Set "lo" period */ 66 #define PCI1760_CMD_SET_PWM_CNT(x) (0x14 + (x)) /* Set burst count */ 67 #define PCI1760_CMD_ENA_PWM 0x1f /* Enable PWM outputs */ 68 #define PCI1760_CMD_ENA_FILT 0x20 /* Enable input filter */ 69 #define PCI1760_CMD_ENA_PAT_MATCH 0x21 /* Enable input pattern match */ 70 #define PCI1760_CMD_SET_PAT_MATCH 0x22 /* Set input pattern match */ 71 #define PCI1760_CMD_ENA_RISE_EDGE 0x23 /* Enable input rising edge */ 72 #define PCI1760_CMD_ENA_FALL_EDGE 0x24 /* Enable input falling edge */ 73 #define PCI1760_CMD_ENA_CNT 0x28 /* Enable counter */ 74 #define PCI1760_CMD_RST_CNT 0x29 /* Reset counter */ 75 #define PCI1760_CMD_ENA_CNT_OFLOW 0x2a /* Enable counter overflow */ 76 #define PCI1760_CMD_ENA_CNT_MATCH 0x2b /* Enable counter match */ 77 #define PCI1760_CMD_SET_CNT_EDGE 0x2c /* Set counter edge */ 78 #define PCI1760_CMD_GET_CNT 0x2f /* Reads counter value */ 79 #define PCI1760_CMD_SET_HI_SAMP(x) (0x30 + (x)) /* Set "hi" sample time */ 80 #define PCI1760_CMD_SET_LO_SAMP(x) (0x38 + (x)) /* Set "lo" sample time */ 81 #define PCI1760_CMD_SET_CNT(x) (0x40 + (x)) /* Set counter reset val */ 82 #define PCI1760_CMD_SET_CNT_MATCH(x) (0x48 + (x)) /* Set counter match val */ 83 #define PCI1760_CMD_GET_INT_FLAGS 0x60 /* Read interrupt flags */ 84 #define PCI1760_CMD_GET_INT_FLAGS_MATCH BIT(0) 85 #define PCI1760_CMD_GET_INT_FLAGS_COS BIT(1) 86 #define PCI1760_CMD_GET_INT_FLAGS_OFLOW BIT(2) 87 #define PCI1760_CMD_GET_OS 0x61 /* Read edge change flags */ 88 #define PCI1760_CMD_GET_CNT_STATUS 0x62 /* Read counter oflow/match */ 89 90 #define PCI1760_CMD_TIMEOUT 250 /* 250 usec timeout */ 91 #define PCI1760_CMD_RETRIES 3 /* limit number of retries */ 92 93 #define PCI1760_PWM_TIMEBASE 100000 /* 1 unit = 100 usec */ 94 95 static int pci1760_send_cmd(struct comedi_device *dev, 96 unsigned char cmd, unsigned short val) 97 { 98 unsigned long timeout; 99 100 /* send the command and parameter */ 101 outb(val & 0xff, dev->iobase + PCI1760_OMB_REG(0)); 102 outb((val >> 8) & 0xff, dev->iobase + PCI1760_OMB_REG(1)); 103 outb(cmd, dev->iobase + PCI1760_OMB_REG(2)); 104 outb(0, dev->iobase + PCI1760_OMB_REG(3)); 105 106 /* datasheet says to allow up to 250 usec for the command to complete */ 107 timeout = jiffies + usecs_to_jiffies(PCI1760_CMD_TIMEOUT); 108 do { 109 if (inb(dev->iobase + PCI1760_IMB_REG(2)) == cmd) { 110 /* command success; return the feedback data */ 111 return inb(dev->iobase + PCI1760_IMB_REG(0)) | 112 (inb(dev->iobase + PCI1760_IMB_REG(1)) << 8); 113 } 114 cpu_relax(); 115 } while (time_before(jiffies, timeout)); 116 117 return -EBUSY; 118 } 119 120 static int pci1760_cmd(struct comedi_device *dev, 121 unsigned char cmd, unsigned short val) 122 { 123 int repeats; 124 int ret; 125 126 /* send PCI1760_CMD_CLR_IMB2 between identical commands */ 127 if (inb(dev->iobase + PCI1760_IMB_REG(2)) == cmd) { 128 ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, 0); 129 if (ret < 0) { 130 /* timeout? try it once more */ 131 ret = pci1760_send_cmd(dev, PCI1760_CMD_CLR_IMB2, 0); 132 if (ret < 0) 133 return -ETIMEDOUT; 134 } 135 } 136 137 /* datasheet says to keep retrying the command */ 138 for (repeats = 0; repeats < PCI1760_CMD_RETRIES; repeats++) { 139 ret = pci1760_send_cmd(dev, cmd, val); 140 if (ret >= 0) 141 return ret; 142 } 143 144 /* command failed! */ 145 return -ETIMEDOUT; 146 } 147 148 static int pci1760_di_insn_bits(struct comedi_device *dev, 149 struct comedi_subdevice *s, 150 struct comedi_insn *insn, 151 unsigned int *data) 152 { 153 data[1] = inb(dev->iobase + PCI1760_IMB_REG(3)); 154 155 return insn->n; 156 } 157 158 static int pci1760_do_insn_bits(struct comedi_device *dev, 159 struct comedi_subdevice *s, 160 struct comedi_insn *insn, 161 unsigned int *data) 162 { 163 int ret; 164 165 if (comedi_dio_update_state(s, data)) { 166 ret = pci1760_cmd(dev, PCI1760_CMD_SET_DO, s->state); 167 if (ret < 0) 168 return ret; 169 } 170 171 data[1] = s->state; 172 173 return insn->n; 174 } 175 176 static int pci1760_pwm_ns_to_div(unsigned int flags, unsigned int ns) 177 { 178 unsigned int divisor; 179 180 switch (flags) { 181 case CMDF_ROUND_NEAREST: 182 divisor = DIV_ROUND_CLOSEST(ns, PCI1760_PWM_TIMEBASE); 183 break; 184 case CMDF_ROUND_UP: 185 divisor = DIV_ROUND_UP(ns, PCI1760_PWM_TIMEBASE); 186 break; 187 case CMDF_ROUND_DOWN: 188 divisor = ns / PCI1760_PWM_TIMEBASE; 189 break; 190 default: 191 return -EINVAL; 192 } 193 194 if (divisor < 1) 195 divisor = 1; 196 if (divisor > 0xffff) 197 divisor = 0xffff; 198 199 return divisor; 200 } 201 202 static int pci1760_pwm_enable(struct comedi_device *dev, 203 unsigned int chan, bool enable) 204 { 205 int ret; 206 207 ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, PCI1760_CMD_ENA_PWM); 208 if (ret < 0) 209 return ret; 210 211 if (enable) 212 ret |= BIT(chan); 213 else 214 ret &= ~BIT(chan); 215 216 return pci1760_cmd(dev, PCI1760_CMD_ENA_PWM, ret); 217 } 218 219 static int pci1760_pwm_insn_config(struct comedi_device *dev, 220 struct comedi_subdevice *s, 221 struct comedi_insn *insn, 222 unsigned int *data) 223 { 224 unsigned int chan = CR_CHAN(insn->chanspec); 225 int hi_div; 226 int lo_div; 227 int ret; 228 229 switch (data[0]) { 230 case INSN_CONFIG_ARM: 231 ret = pci1760_pwm_enable(dev, chan, false); 232 if (ret < 0) 233 return ret; 234 235 if (data[1] > 0xffff) 236 return -EINVAL; 237 ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_CNT(chan), data[1]); 238 if (ret < 0) 239 return ret; 240 241 ret = pci1760_pwm_enable(dev, chan, true); 242 if (ret < 0) 243 return ret; 244 break; 245 case INSN_CONFIG_DISARM: 246 ret = pci1760_pwm_enable(dev, chan, false); 247 if (ret < 0) 248 return ret; 249 break; 250 case INSN_CONFIG_PWM_OUTPUT: 251 ret = pci1760_pwm_enable(dev, chan, false); 252 if (ret < 0) 253 return ret; 254 255 hi_div = pci1760_pwm_ns_to_div(data[1], data[2]); 256 lo_div = pci1760_pwm_ns_to_div(data[3], data[4]); 257 if (hi_div < 0 || lo_div < 0) 258 return -EINVAL; 259 if ((hi_div * PCI1760_PWM_TIMEBASE) != data[2] || 260 (lo_div * PCI1760_PWM_TIMEBASE) != data[4]) { 261 data[2] = hi_div * PCI1760_PWM_TIMEBASE; 262 data[4] = lo_div * PCI1760_PWM_TIMEBASE; 263 return -EAGAIN; 264 } 265 ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_HI(chan), hi_div); 266 if (ret < 0) 267 return ret; 268 ret = pci1760_cmd(dev, PCI1760_CMD_SET_PWM_LO(chan), lo_div); 269 if (ret < 0) 270 return ret; 271 break; 272 case INSN_CONFIG_GET_PWM_OUTPUT: 273 hi_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, 274 PCI1760_CMD_SET_PWM_HI(chan)); 275 lo_div = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, 276 PCI1760_CMD_SET_PWM_LO(chan)); 277 if (hi_div < 0 || lo_div < 0) 278 return -ETIMEDOUT; 279 280 data[1] = hi_div * PCI1760_PWM_TIMEBASE; 281 data[2] = lo_div * PCI1760_PWM_TIMEBASE; 282 break; 283 case INSN_CONFIG_GET_PWM_STATUS: 284 ret = pci1760_cmd(dev, PCI1760_CMD_GET_STATUS, 285 PCI1760_CMD_ENA_PWM); 286 if (ret < 0) 287 return ret; 288 289 data[1] = (ret & BIT(chan)) ? 1 : 0; 290 break; 291 default: 292 return -EINVAL; 293 } 294 295 return insn->n; 296 } 297 298 static void pci1760_reset(struct comedi_device *dev) 299 { 300 int i; 301 302 /* disable interrupts (intcsr2 is read-only) */ 303 outb(0, dev->iobase + PCI1760_INTCSR_REG(0)); 304 outb(0, dev->iobase + PCI1760_INTCSR_REG(1)); 305 outb(0, dev->iobase + PCI1760_INTCSR_REG(3)); 306 307 /* disable counters */ 308 pci1760_cmd(dev, PCI1760_CMD_ENA_CNT, 0); 309 310 /* disable overflow interrupts */ 311 pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_OFLOW, 0); 312 313 /* disable match */ 314 pci1760_cmd(dev, PCI1760_CMD_ENA_CNT_MATCH, 0); 315 316 /* set match and counter reset values */ 317 for (i = 0; i < 8; i++) { 318 pci1760_cmd(dev, PCI1760_CMD_SET_CNT_MATCH(i), 0x8000); 319 pci1760_cmd(dev, PCI1760_CMD_SET_CNT(i), 0x0000); 320 } 321 322 /* reset counters to reset values */ 323 pci1760_cmd(dev, PCI1760_CMD_RST_CNT, 0xff); 324 325 /* set counter count edges */ 326 pci1760_cmd(dev, PCI1760_CMD_SET_CNT_EDGE, 0); 327 328 /* disable input filters */ 329 pci1760_cmd(dev, PCI1760_CMD_ENA_FILT, 0); 330 331 /* disable pattern matching */ 332 pci1760_cmd(dev, PCI1760_CMD_ENA_PAT_MATCH, 0); 333 334 /* set pattern match value */ 335 pci1760_cmd(dev, PCI1760_CMD_SET_PAT_MATCH, 0); 336 } 337 338 static int pci1760_auto_attach(struct comedi_device *dev, 339 unsigned long context) 340 { 341 struct pci_dev *pcidev = comedi_to_pci_dev(dev); 342 struct comedi_subdevice *s; 343 int ret; 344 345 ret = comedi_pci_enable(dev); 346 if (ret) 347 return ret; 348 dev->iobase = pci_resource_start(pcidev, 0); 349 350 pci1760_reset(dev); 351 352 ret = comedi_alloc_subdevices(dev, 4); 353 if (ret) 354 return ret; 355 356 /* Digital Input subdevice */ 357 s = &dev->subdevices[0]; 358 s->type = COMEDI_SUBD_DI; 359 s->subdev_flags = SDF_READABLE; 360 s->n_chan = 8; 361 s->maxdata = 1; 362 s->range_table = &range_digital; 363 s->insn_bits = pci1760_di_insn_bits; 364 365 /* Digital Output subdevice */ 366 s = &dev->subdevices[1]; 367 s->type = COMEDI_SUBD_DO; 368 s->subdev_flags = SDF_WRITABLE; 369 s->n_chan = 8; 370 s->maxdata = 1; 371 s->range_table = &range_digital; 372 s->insn_bits = pci1760_do_insn_bits; 373 374 /* get the current state of the outputs */ 375 ret = pci1760_cmd(dev, PCI1760_CMD_GET_DO, 0); 376 if (ret < 0) 377 return ret; 378 s->state = ret; 379 380 /* PWM subdevice */ 381 s = &dev->subdevices[2]; 382 s->type = COMEDI_SUBD_PWM; 383 s->subdev_flags = SDF_PWM_COUNTER; 384 s->n_chan = 2; 385 s->insn_config = pci1760_pwm_insn_config; 386 387 /* Counter subdevice */ 388 s = &dev->subdevices[3]; 389 s->type = COMEDI_SUBD_UNUSED; 390 391 return 0; 392 } 393 394 static struct comedi_driver pci1760_driver = { 395 .driver_name = "adv_pci1760", 396 .module = THIS_MODULE, 397 .auto_attach = pci1760_auto_attach, 398 .detach = comedi_pci_detach, 399 }; 400 401 static int pci1760_pci_probe(struct pci_dev *dev, 402 const struct pci_device_id *id) 403 { 404 return comedi_pci_auto_config(dev, &pci1760_driver, id->driver_data); 405 } 406 407 static const struct pci_device_id pci1760_pci_table[] = { 408 { PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1760) }, 409 { 0 } 410 }; 411 MODULE_DEVICE_TABLE(pci, pci1760_pci_table); 412 413 static struct pci_driver pci1760_pci_driver = { 414 .name = "adv_pci1760", 415 .id_table = pci1760_pci_table, 416 .probe = pci1760_pci_probe, 417 .remove = comedi_pci_auto_unconfig, 418 }; 419 module_comedi_pci_driver(pci1760_driver, pci1760_pci_driver); 420 421 MODULE_AUTHOR("Comedi https://www.comedi.org"); 422 MODULE_DESCRIPTION("Comedi driver for Advantech PCI-1760"); 423 MODULE_LICENSE("GPL"); 424