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