| 1 | // SPDX-License-Identifier: GPL-2.0+ |
| 2 | /* |
| 3 | * pcmuio.c |
| 4 | * Comedi driver for Winsystems PC-104 based 48/96-channel DIO boards. |
| 5 | * |
| 6 | * COMEDI - Linux Control and Measurement Device Interface |
| 7 | * Copyright (C) 2006 Calin A. Culianu <calin@ajvar.org> |
| 8 | */ |
| 9 | |
| 10 | /* |
| 11 | * Driver: pcmuio |
| 12 | * Description: Winsystems PC-104 based 48/96-channel DIO boards. |
| 13 | * Devices: [Winsystems] PCM-UIO48A (pcmuio48), PCM-UIO96A (pcmuio96) |
| 14 | * Author: Calin Culianu <calin@ajvar.org> |
| 15 | * Updated: Fri, 13 Jan 2006 12:01:01 -0500 |
| 16 | * Status: works |
| 17 | * |
| 18 | * A driver for the relatively straightforward-to-program PCM-UIO48A and |
| 19 | * PCM-UIO96A boards from Winsystems. These boards use either one or two |
| 20 | * (in the 96-DIO version) WS16C48 ASIC HighDensity I/O Chips (HDIO). This |
| 21 | * chip is interesting in that each I/O line is individually programmable |
| 22 | * for INPUT or OUTPUT (thus comedi_dio_config can be done on a per-channel |
| 23 | * basis). Also, each chip supports edge-triggered interrupts for the first |
| 24 | * 24 I/O lines. Of course, since the 96-channel version of the board has |
| 25 | * two ASICs, it can detect polarity changes on up to 48 I/O lines. Since |
| 26 | * this is essentially an (non-PnP) ISA board, I/O Address and IRQ selection |
| 27 | * are done through jumpers on the board. You need to pass that information |
| 28 | * to this driver as the first and second comedi_config option, respectively. |
| 29 | * Note that the 48-channel version uses 16 bytes of IO memory and the 96- |
| 30 | * channel version uses 32-bytes (in case you are worried about conflicts). |
| 31 | * The 48-channel board is split into two 24-channel comedi subdevices. The |
| 32 | * 96-channel board is split into 4 24-channel DIO subdevices. |
| 33 | * |
| 34 | * Note that IRQ support has been added, but it is untested. |
| 35 | * |
| 36 | * To use edge-detection IRQ support, pass the IRQs of both ASICS (for the |
| 37 | * 96 channel version) or just 1 ASIC (for 48-channel version). Then, use |
| 38 | * comedi_commands with TRIG_NOW. Your callback will be called each time an |
| 39 | * edge is triggered, and the data values will be two sample_t's, which |
| 40 | * should be concatenated to form one 32-bit unsigned int. This value is |
| 41 | * the mask of channels that had edges detected from your channel list. Note |
| 42 | * that the bits positions in the mask correspond to positions in your |
| 43 | * chanlist when you specified the command and *not* channel id's! |
| 44 | * |
| 45 | * To set the polarity of the edge-detection interrupts pass a nonzero value |
| 46 | * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero value for |
| 47 | * both CR_RANGE and CR_AREF if you want edge-down polarity. |
| 48 | * |
| 49 | * In the 48-channel version: |
| 50 | * |
| 51 | * On subdev 0, the first 24 channels are edge-detect channels. |
| 52 | * |
| 53 | * In the 96-channel board you have the following channels that can do edge |
| 54 | * detection: |
| 55 | * |
| 56 | * subdev 0, channels 0-24 (first 24 channels of 1st ASIC) |
| 57 | * subdev 2, channels 0-24 (first 24 channels of 2nd ASIC) |
| 58 | * |
| 59 | * Configuration Options: |
| 60 | * [0] - I/O port base address |
| 61 | * [1] - IRQ (for first ASIC, or first 24 channels) |
| 62 | * [2] - IRQ (for second ASIC, pcmuio96 only - IRQ for chans 48-72 |
| 63 | * can be the same as first irq!) |
| 64 | */ |
| 65 | |
| 66 | #include <linux/module.h> |
| 67 | #include <linux/interrupt.h> |
| 68 | #include <linux/comedi/comedidev.h> |
| 69 | |
| 70 | /* |
| 71 | * Register I/O map |
| 72 | * |
| 73 | * Offset Page 0 Page 1 Page 2 Page 3 |
| 74 | * ------ ----------- ----------- ----------- ----------- |
| 75 | * 0x00 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O |
| 76 | * 0x01 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O |
| 77 | * 0x02 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O |
| 78 | * 0x03 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O |
| 79 | * 0x04 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O |
| 80 | * 0x05 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O |
| 81 | * 0x06 INT_PENDING INT_PENDING INT_PENDING INT_PENDING |
| 82 | * 0x07 Page/Lock Page/Lock Page/Lock Page/Lock |
| 83 | * 0x08 N/A POL_0 ENAB_0 INT_ID0 |
| 84 | * 0x09 N/A POL_1 ENAB_1 INT_ID1 |
| 85 | * 0x0a N/A POL_2 ENAB_2 INT_ID2 |
| 86 | */ |
| 87 | #define PCMUIO_PORT_REG(x) (0x00 + (x)) |
| 88 | #define PCMUIO_INT_PENDING_REG 0x06 |
| 89 | #define PCMUIO_PAGE_LOCK_REG 0x07 |
| 90 | #define PCMUIO_LOCK_PORT(x) ((1 << (x)) & 0x3f) |
| 91 | #define PCMUIO_PAGE(x) (((x) & 0x3) << 6) |
| 92 | #define PCMUIO_PAGE_MASK PCMUIO_PAGE(3) |
| 93 | #define PCMUIO_PAGE_POL 1 |
| 94 | #define PCMUIO_PAGE_ENAB 2 |
| 95 | #define PCMUIO_PAGE_INT_ID 3 |
| 96 | #define PCMUIO_PAGE_REG(x) (0x08 + (x)) |
| 97 | |
| 98 | #define PCMUIO_ASIC_IOSIZE 0x10 |
| 99 | #define PCMUIO_MAX_ASICS 2 |
| 100 | |
| 101 | struct pcmuio_board { |
| 102 | const char *name; |
| 103 | const int num_asics; |
| 104 | }; |
| 105 | |
| 106 | static const struct pcmuio_board pcmuio_boards[] = { |
| 107 | { |
| 108 | .name = "pcmuio48" , |
| 109 | .num_asics = 1, |
| 110 | }, { |
| 111 | .name = "pcmuio96" , |
| 112 | .num_asics = 2, |
| 113 | }, |
| 114 | }; |
| 115 | |
| 116 | struct pcmuio_asic { |
| 117 | spinlock_t pagelock; /* protects the page registers */ |
| 118 | spinlock_t spinlock; /* protects member variables */ |
| 119 | unsigned int enabled_mask; |
| 120 | unsigned int active:1; |
| 121 | }; |
| 122 | |
| 123 | struct pcmuio_private { |
| 124 | struct pcmuio_asic asics[PCMUIO_MAX_ASICS]; |
| 125 | unsigned int irq2; |
| 126 | }; |
| 127 | |
| 128 | static inline unsigned long pcmuio_asic_iobase(struct comedi_device *dev, |
| 129 | int asic) |
| 130 | { |
| 131 | return dev->iobase + (asic * PCMUIO_ASIC_IOSIZE); |
| 132 | } |
| 133 | |
| 134 | static inline int pcmuio_subdevice_to_asic(struct comedi_subdevice *s) |
| 135 | { |
| 136 | /* |
| 137 | * subdevice 0 and 1 are handled by the first asic |
| 138 | * subdevice 2 and 3 are handled by the second asic |
| 139 | */ |
| 140 | return s->index / 2; |
| 141 | } |
| 142 | |
| 143 | static inline int pcmuio_subdevice_to_port(struct comedi_subdevice *s) |
| 144 | { |
| 145 | /* |
| 146 | * subdevice 0 and 2 use port registers 0-2 |
| 147 | * subdevice 1 and 3 use port registers 3-5 |
| 148 | */ |
| 149 | return (s->index % 2) ? 3 : 0; |
| 150 | } |
| 151 | |
| 152 | static void pcmuio_write(struct comedi_device *dev, unsigned int val, |
| 153 | int asic, int page, int port) |
| 154 | { |
| 155 | struct pcmuio_private *devpriv = dev->private; |
| 156 | struct pcmuio_asic *chip = &devpriv->asics[asic]; |
| 157 | unsigned long iobase = pcmuio_asic_iobase(dev, asic); |
| 158 | unsigned long flags; |
| 159 | |
| 160 | spin_lock_irqsave(&chip->pagelock, flags); |
| 161 | if (page == 0) { |
| 162 | /* Port registers are valid for any page */ |
| 163 | outb(value: val & 0xff, port: iobase + PCMUIO_PORT_REG(port + 0)); |
| 164 | outb(value: (val >> 8) & 0xff, port: iobase + PCMUIO_PORT_REG(port + 1)); |
| 165 | outb(value: (val >> 16) & 0xff, port: iobase + PCMUIO_PORT_REG(port + 2)); |
| 166 | } else { |
| 167 | outb(PCMUIO_PAGE(page), port: iobase + PCMUIO_PAGE_LOCK_REG); |
| 168 | outb(value: val & 0xff, port: iobase + PCMUIO_PAGE_REG(0)); |
| 169 | outb(value: (val >> 8) & 0xff, port: iobase + PCMUIO_PAGE_REG(1)); |
| 170 | outb(value: (val >> 16) & 0xff, port: iobase + PCMUIO_PAGE_REG(2)); |
| 171 | } |
| 172 | spin_unlock_irqrestore(lock: &chip->pagelock, flags); |
| 173 | } |
| 174 | |
| 175 | static unsigned int pcmuio_read(struct comedi_device *dev, |
| 176 | int asic, int page, int port) |
| 177 | { |
| 178 | struct pcmuio_private *devpriv = dev->private; |
| 179 | struct pcmuio_asic *chip = &devpriv->asics[asic]; |
| 180 | unsigned long iobase = pcmuio_asic_iobase(dev, asic); |
| 181 | unsigned long flags; |
| 182 | unsigned int val; |
| 183 | |
| 184 | spin_lock_irqsave(&chip->pagelock, flags); |
| 185 | if (page == 0) { |
| 186 | /* Port registers are valid for any page */ |
| 187 | val = inb(port: iobase + PCMUIO_PORT_REG(port + 0)); |
| 188 | val |= (inb(port: iobase + PCMUIO_PORT_REG(port + 1)) << 8); |
| 189 | val |= (inb(port: iobase + PCMUIO_PORT_REG(port + 2)) << 16); |
| 190 | } else { |
| 191 | outb(PCMUIO_PAGE(page), port: iobase + PCMUIO_PAGE_LOCK_REG); |
| 192 | val = inb(port: iobase + PCMUIO_PAGE_REG(0)); |
| 193 | val |= (inb(port: iobase + PCMUIO_PAGE_REG(1)) << 8); |
| 194 | val |= (inb(port: iobase + PCMUIO_PAGE_REG(2)) << 16); |
| 195 | } |
| 196 | spin_unlock_irqrestore(lock: &chip->pagelock, flags); |
| 197 | |
| 198 | return val; |
| 199 | } |
| 200 | |
| 201 | /* |
| 202 | * Each channel can be individually programmed for input or output. |
| 203 | * Writing a '0' to a channel causes the corresponding output pin |
| 204 | * to go to a high-z state (pulled high by an external 10K resistor). |
| 205 | * This allows it to be used as an input. When used in the input mode, |
| 206 | * a read reflects the inverted state of the I/O pin, such that a |
| 207 | * high on the pin will read as a '0' in the register. Writing a '1' |
| 208 | * to a bit position causes the pin to sink current (up to 12mA), |
| 209 | * effectively pulling it low. |
| 210 | */ |
| 211 | static int pcmuio_dio_insn_bits(struct comedi_device *dev, |
| 212 | struct comedi_subdevice *s, |
| 213 | struct comedi_insn *insn, |
| 214 | unsigned int *data) |
| 215 | { |
| 216 | int asic = pcmuio_subdevice_to_asic(s); |
| 217 | int port = pcmuio_subdevice_to_port(s); |
| 218 | unsigned int chanmask = (1 << s->n_chan) - 1; |
| 219 | unsigned int mask; |
| 220 | unsigned int val; |
| 221 | |
| 222 | mask = comedi_dio_update_state(s, data); |
| 223 | if (mask) { |
| 224 | /* |
| 225 | * Outputs are inverted, invert the state and |
| 226 | * update the channels. |
| 227 | * |
| 228 | * The s->io_bits mask makes sure the input channels |
| 229 | * are '0' so that the outputs pins stay in a high |
| 230 | * z-state. |
| 231 | */ |
| 232 | val = ~s->state & chanmask; |
| 233 | val &= s->io_bits; |
| 234 | pcmuio_write(dev, val, asic, page: 0, port); |
| 235 | } |
| 236 | |
| 237 | /* get inverted state of the channels from the port */ |
| 238 | val = pcmuio_read(dev, asic, page: 0, port); |
| 239 | |
| 240 | /* return the true state of the channels */ |
| 241 | data[1] = ~val & chanmask; |
| 242 | |
| 243 | return insn->n; |
| 244 | } |
| 245 | |
| 246 | static int pcmuio_dio_insn_config(struct comedi_device *dev, |
| 247 | struct comedi_subdevice *s, |
| 248 | struct comedi_insn *insn, |
| 249 | unsigned int *data) |
| 250 | { |
| 251 | int asic = pcmuio_subdevice_to_asic(s); |
| 252 | int port = pcmuio_subdevice_to_port(s); |
| 253 | int ret; |
| 254 | |
| 255 | ret = comedi_dio_insn_config(dev, s, insn, data, mask: 0); |
| 256 | if (ret) |
| 257 | return ret; |
| 258 | |
| 259 | if (data[0] == INSN_CONFIG_DIO_INPUT) |
| 260 | pcmuio_write(dev, val: s->io_bits, asic, page: 0, port); |
| 261 | |
| 262 | return insn->n; |
| 263 | } |
| 264 | |
| 265 | static void pcmuio_reset(struct comedi_device *dev) |
| 266 | { |
| 267 | const struct pcmuio_board *board = dev->board_ptr; |
| 268 | int asic; |
| 269 | |
| 270 | for (asic = 0; asic < board->num_asics; ++asic) { |
| 271 | /* first, clear all the DIO port bits */ |
| 272 | pcmuio_write(dev, val: 0, asic, page: 0, port: 0); |
| 273 | pcmuio_write(dev, val: 0, asic, page: 0, port: 3); |
| 274 | |
| 275 | /* Next, clear all the paged registers for each page */ |
| 276 | pcmuio_write(dev, val: 0, asic, PCMUIO_PAGE_POL, port: 0); |
| 277 | pcmuio_write(dev, val: 0, asic, PCMUIO_PAGE_ENAB, port: 0); |
| 278 | pcmuio_write(dev, val: 0, asic, PCMUIO_PAGE_INT_ID, port: 0); |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | /* chip->spinlock is already locked */ |
| 283 | static void pcmuio_stop_intr(struct comedi_device *dev, |
| 284 | struct comedi_subdevice *s) |
| 285 | { |
| 286 | struct pcmuio_private *devpriv = dev->private; |
| 287 | int asic = pcmuio_subdevice_to_asic(s); |
| 288 | struct pcmuio_asic *chip = &devpriv->asics[asic]; |
| 289 | |
| 290 | chip->enabled_mask = 0; |
| 291 | chip->active = 0; |
| 292 | s->async->inttrig = NULL; |
| 293 | |
| 294 | /* disable all intrs for this subdev.. */ |
| 295 | pcmuio_write(dev, val: 0, asic, PCMUIO_PAGE_ENAB, port: 0); |
| 296 | } |
| 297 | |
| 298 | static void pcmuio_handle_intr_subdev(struct comedi_device *dev, |
| 299 | struct comedi_subdevice *s, |
| 300 | unsigned int triggered) |
| 301 | { |
| 302 | struct pcmuio_private *devpriv = dev->private; |
| 303 | int asic = pcmuio_subdevice_to_asic(s); |
| 304 | struct pcmuio_asic *chip = &devpriv->asics[asic]; |
| 305 | struct comedi_cmd *cmd = &s->async->cmd; |
| 306 | unsigned int val = 0; |
| 307 | unsigned long flags; |
| 308 | unsigned int i; |
| 309 | |
| 310 | spin_lock_irqsave(&chip->spinlock, flags); |
| 311 | |
| 312 | if (!chip->active) |
| 313 | goto done; |
| 314 | |
| 315 | if (!(triggered & chip->enabled_mask)) |
| 316 | goto done; |
| 317 | |
| 318 | for (i = 0; i < cmd->chanlist_len; i++) { |
| 319 | unsigned int chan = CR_CHAN(cmd->chanlist[i]); |
| 320 | |
| 321 | if (triggered & (1 << chan)) |
| 322 | val |= (1 << i); |
| 323 | } |
| 324 | |
| 325 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
| 326 | |
| 327 | if (cmd->stop_src == TRIG_COUNT && |
| 328 | s->async->scans_done >= cmd->stop_arg) |
| 329 | s->async->events |= COMEDI_CB_EOA; |
| 330 | |
| 331 | done: |
| 332 | spin_unlock_irqrestore(lock: &chip->spinlock, flags); |
| 333 | |
| 334 | comedi_handle_events(dev, s); |
| 335 | } |
| 336 | |
| 337 | static int pcmuio_handle_asic_interrupt(struct comedi_device *dev, int asic) |
| 338 | { |
| 339 | /* there are could be two asics so we can't use dev->read_subdev */ |
| 340 | struct comedi_subdevice *s = &dev->subdevices[asic * 2]; |
| 341 | unsigned long iobase = pcmuio_asic_iobase(dev, asic); |
| 342 | unsigned int val; |
| 343 | |
| 344 | /* are there any interrupts pending */ |
| 345 | val = inb(port: iobase + PCMUIO_INT_PENDING_REG) & 0x07; |
| 346 | if (!val) |
| 347 | return 0; |
| 348 | |
| 349 | /* get, and clear, the pending interrupts */ |
| 350 | val = pcmuio_read(dev, asic, PCMUIO_PAGE_INT_ID, port: 0); |
| 351 | pcmuio_write(dev, val: 0, asic, PCMUIO_PAGE_INT_ID, port: 0); |
| 352 | |
| 353 | /* handle the pending interrupts */ |
| 354 | pcmuio_handle_intr_subdev(dev, s, triggered: val); |
| 355 | |
| 356 | return 1; |
| 357 | } |
| 358 | |
| 359 | static irqreturn_t pcmuio_interrupt(int irq, void *d) |
| 360 | { |
| 361 | struct comedi_device *dev = d; |
| 362 | struct pcmuio_private *devpriv = dev->private; |
| 363 | int handled = 0; |
| 364 | |
| 365 | if (irq == dev->irq) |
| 366 | handled += pcmuio_handle_asic_interrupt(dev, asic: 0); |
| 367 | if (irq == devpriv->irq2) |
| 368 | handled += pcmuio_handle_asic_interrupt(dev, asic: 1); |
| 369 | |
| 370 | return handled ? IRQ_HANDLED : IRQ_NONE; |
| 371 | } |
| 372 | |
| 373 | /* chip->spinlock is already locked */ |
| 374 | static void pcmuio_start_intr(struct comedi_device *dev, |
| 375 | struct comedi_subdevice *s) |
| 376 | { |
| 377 | struct pcmuio_private *devpriv = dev->private; |
| 378 | int asic = pcmuio_subdevice_to_asic(s); |
| 379 | struct pcmuio_asic *chip = &devpriv->asics[asic]; |
| 380 | struct comedi_cmd *cmd = &s->async->cmd; |
| 381 | unsigned int bits = 0; |
| 382 | unsigned int pol_bits = 0; |
| 383 | int i; |
| 384 | |
| 385 | chip->enabled_mask = 0; |
| 386 | chip->active = 1; |
| 387 | if (cmd->chanlist) { |
| 388 | for (i = 0; i < cmd->chanlist_len; i++) { |
| 389 | unsigned int chanspec = cmd->chanlist[i]; |
| 390 | unsigned int chan = CR_CHAN(chanspec); |
| 391 | unsigned int range = CR_RANGE(chanspec); |
| 392 | unsigned int aref = CR_AREF(chanspec); |
| 393 | |
| 394 | bits |= (1 << chan); |
| 395 | pol_bits |= ((aref || range) ? 1 : 0) << chan; |
| 396 | } |
| 397 | } |
| 398 | bits &= ((1 << s->n_chan) - 1); |
| 399 | chip->enabled_mask = bits; |
| 400 | |
| 401 | /* set pol and enab intrs for this subdev.. */ |
| 402 | pcmuio_write(dev, val: pol_bits, asic, PCMUIO_PAGE_POL, port: 0); |
| 403 | pcmuio_write(dev, val: bits, asic, PCMUIO_PAGE_ENAB, port: 0); |
| 404 | } |
| 405 | |
| 406 | static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) |
| 407 | { |
| 408 | struct pcmuio_private *devpriv = dev->private; |
| 409 | int asic = pcmuio_subdevice_to_asic(s); |
| 410 | struct pcmuio_asic *chip = &devpriv->asics[asic]; |
| 411 | unsigned long flags; |
| 412 | |
| 413 | spin_lock_irqsave(&chip->spinlock, flags); |
| 414 | if (chip->active) |
| 415 | pcmuio_stop_intr(dev, s); |
| 416 | spin_unlock_irqrestore(lock: &chip->spinlock, flags); |
| 417 | |
| 418 | return 0; |
| 419 | } |
| 420 | |
| 421 | static int pcmuio_inttrig_start_intr(struct comedi_device *dev, |
| 422 | struct comedi_subdevice *s, |
| 423 | unsigned int trig_num) |
| 424 | { |
| 425 | struct pcmuio_private *devpriv = dev->private; |
| 426 | struct comedi_cmd *cmd = &s->async->cmd; |
| 427 | int asic = pcmuio_subdevice_to_asic(s); |
| 428 | struct pcmuio_asic *chip = &devpriv->asics[asic]; |
| 429 | unsigned long flags; |
| 430 | |
| 431 | if (trig_num != cmd->start_arg) |
| 432 | return -EINVAL; |
| 433 | |
| 434 | spin_lock_irqsave(&chip->spinlock, flags); |
| 435 | s->async->inttrig = NULL; |
| 436 | if (chip->active) |
| 437 | pcmuio_start_intr(dev, s); |
| 438 | |
| 439 | spin_unlock_irqrestore(lock: &chip->spinlock, flags); |
| 440 | |
| 441 | return 1; |
| 442 | } |
| 443 | |
| 444 | /* |
| 445 | * 'do_cmd' function for an 'INTERRUPT' subdevice. |
| 446 | */ |
| 447 | static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
| 448 | { |
| 449 | struct pcmuio_private *devpriv = dev->private; |
| 450 | struct comedi_cmd *cmd = &s->async->cmd; |
| 451 | int asic = pcmuio_subdevice_to_asic(s); |
| 452 | struct pcmuio_asic *chip = &devpriv->asics[asic]; |
| 453 | unsigned long flags; |
| 454 | |
| 455 | spin_lock_irqsave(&chip->spinlock, flags); |
| 456 | chip->active = 1; |
| 457 | |
| 458 | /* Set up start of acquisition. */ |
| 459 | if (cmd->start_src == TRIG_INT) |
| 460 | s->async->inttrig = pcmuio_inttrig_start_intr; |
| 461 | else /* TRIG_NOW */ |
| 462 | pcmuio_start_intr(dev, s); |
| 463 | |
| 464 | spin_unlock_irqrestore(lock: &chip->spinlock, flags); |
| 465 | |
| 466 | return 0; |
| 467 | } |
| 468 | |
| 469 | static int pcmuio_cmdtest(struct comedi_device *dev, |
| 470 | struct comedi_subdevice *s, |
| 471 | struct comedi_cmd *cmd) |
| 472 | { |
| 473 | int err = 0; |
| 474 | |
| 475 | /* Step 1 : check if triggers are trivially valid */ |
| 476 | |
| 477 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW | TRIG_INT); |
| 478 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_EXT); |
| 479 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_NOW); |
| 480 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
| 481 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_COUNT | TRIG_NONE); |
| 482 | |
| 483 | if (err) |
| 484 | return 1; |
| 485 | |
| 486 | /* Step 2a : make sure trigger sources are unique */ |
| 487 | |
| 488 | err |= comedi_check_trigger_is_unique(src: cmd->start_src); |
| 489 | err |= comedi_check_trigger_is_unique(src: cmd->stop_src); |
| 490 | |
| 491 | /* Step 2b : and mutually compatible */ |
| 492 | |
| 493 | if (err) |
| 494 | return 2; |
| 495 | |
| 496 | /* Step 3: check if arguments are trivially valid */ |
| 497 | |
| 498 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
| 499 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
| 500 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
| 501 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
| 502 | val: cmd->chanlist_len); |
| 503 | |
| 504 | if (cmd->stop_src == TRIG_COUNT) |
| 505 | err |= comedi_check_trigger_arg_min(arg: &cmd->stop_arg, val: 1); |
| 506 | else /* TRIG_NONE */ |
| 507 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
| 508 | |
| 509 | if (err) |
| 510 | return 3; |
| 511 | |
| 512 | /* step 4: fix up any arguments */ |
| 513 | |
| 514 | /* if (err) return 4; */ |
| 515 | |
| 516 | return 0; |
| 517 | } |
| 518 | |
| 519 | static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
| 520 | { |
| 521 | const struct pcmuio_board *board = dev->board_ptr; |
| 522 | struct comedi_subdevice *s; |
| 523 | struct pcmuio_private *devpriv; |
| 524 | int ret; |
| 525 | int i; |
| 526 | |
| 527 | ret = comedi_request_region(dev, start: it->options[0], |
| 528 | len: board->num_asics * PCMUIO_ASIC_IOSIZE); |
| 529 | if (ret) |
| 530 | return ret; |
| 531 | |
| 532 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
| 533 | if (!devpriv) |
| 534 | return -ENOMEM; |
| 535 | |
| 536 | for (i = 0; i < PCMUIO_MAX_ASICS; ++i) { |
| 537 | struct pcmuio_asic *chip = &devpriv->asics[i]; |
| 538 | |
| 539 | spin_lock_init(&chip->pagelock); |
| 540 | spin_lock_init(&chip->spinlock); |
| 541 | } |
| 542 | |
| 543 | pcmuio_reset(dev); |
| 544 | |
| 545 | if (it->options[1]) { |
| 546 | /* request the irq for the 1st asic */ |
| 547 | ret = request_irq(irq: it->options[1], handler: pcmuio_interrupt, flags: 0, |
| 548 | name: dev->board_name, dev); |
| 549 | if (ret == 0) |
| 550 | dev->irq = it->options[1]; |
| 551 | } |
| 552 | |
| 553 | if (board->num_asics == 2) { |
| 554 | if (it->options[2] == dev->irq) { |
| 555 | /* the same irq (or none) is used by both asics */ |
| 556 | devpriv->irq2 = it->options[2]; |
| 557 | } else if (it->options[2]) { |
| 558 | /* request the irq for the 2nd asic */ |
| 559 | ret = request_irq(irq: it->options[2], handler: pcmuio_interrupt, flags: 0, |
| 560 | name: dev->board_name, dev); |
| 561 | if (ret == 0) |
| 562 | devpriv->irq2 = it->options[2]; |
| 563 | } |
| 564 | } |
| 565 | |
| 566 | ret = comedi_alloc_subdevices(dev, num_subdevices: board->num_asics * 2); |
| 567 | if (ret) |
| 568 | return ret; |
| 569 | |
| 570 | for (i = 0; i < dev->n_subdevices; ++i) { |
| 571 | s = &dev->subdevices[i]; |
| 572 | s->type = COMEDI_SUBD_DIO; |
| 573 | s->subdev_flags = SDF_READABLE | SDF_WRITABLE; |
| 574 | s->n_chan = 24; |
| 575 | s->maxdata = 1; |
| 576 | s->range_table = &range_digital; |
| 577 | s->insn_bits = pcmuio_dio_insn_bits; |
| 578 | s->insn_config = pcmuio_dio_insn_config; |
| 579 | |
| 580 | /* subdevices 0 and 2 can support interrupts */ |
| 581 | if ((i == 0 && dev->irq) || (i == 2 && devpriv->irq2)) { |
| 582 | /* setup the interrupt subdevice */ |
| 583 | dev->read_subdev = s; |
| 584 | s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL | |
| 585 | SDF_PACKED; |
| 586 | s->len_chanlist = s->n_chan; |
| 587 | s->cancel = pcmuio_cancel; |
| 588 | s->do_cmd = pcmuio_cmd; |
| 589 | s->do_cmdtest = pcmuio_cmdtest; |
| 590 | } |
| 591 | } |
| 592 | |
| 593 | return 0; |
| 594 | } |
| 595 | |
| 596 | static void pcmuio_detach(struct comedi_device *dev) |
| 597 | { |
| 598 | struct pcmuio_private *devpriv = dev->private; |
| 599 | |
| 600 | if (devpriv) { |
| 601 | pcmuio_reset(dev); |
| 602 | |
| 603 | /* free the 2nd irq if used, the core will free the 1st one */ |
| 604 | if (devpriv->irq2 && devpriv->irq2 != dev->irq) |
| 605 | free_irq(devpriv->irq2, dev); |
| 606 | } |
| 607 | comedi_legacy_detach(dev); |
| 608 | } |
| 609 | |
| 610 | static struct comedi_driver pcmuio_driver = { |
| 611 | .driver_name = "pcmuio" , |
| 612 | .module = THIS_MODULE, |
| 613 | .attach = pcmuio_attach, |
| 614 | .detach = pcmuio_detach, |
| 615 | .board_name = &pcmuio_boards[0].name, |
| 616 | .offset = sizeof(struct pcmuio_board), |
| 617 | .num_names = ARRAY_SIZE(pcmuio_boards), |
| 618 | }; |
| 619 | module_comedi_driver(pcmuio_driver); |
| 620 | |
| 621 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
| 622 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
| 623 | MODULE_LICENSE("GPL" ); |
| 624 | |