3 * Comedi driver for Winsystems PC-104 based 48/96-channel DIO boards.
5 * COMEDI - Linux Control and Measurement Device Interface
6 * Copyright (C) 2006 Calin A. Culianu <calin@ajvar.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
21 * Description: Winsystems PC-104 based 48/96-channel DIO boards.
22 * Devices: (Winsystems) PCM-UIO48A [pcmuio48]
23 * (Winsystems) PCM-UIO96A [pcmuio96]
24 * Author: Calin Culianu <calin@ajvar.org>
25 * Updated: Fri, 13 Jan 2006 12:01:01 -0500
28 * A driver for the relatively straightforward-to-program PCM-UIO48A and
29 * PCM-UIO96A boards from Winsystems. These boards use either one or two
30 * (in the 96-DIO version) WS16C48 ASIC HighDensity I/O Chips (HDIO). This
31 * chip is interesting in that each I/O line is individually programmable
32 * for INPUT or OUTPUT (thus comedi_dio_config can be done on a per-channel
33 * basis). Also, each chip supports edge-triggered interrupts for the first
34 * 24 I/O lines. Of course, since the 96-channel version of the board has
35 * two ASICs, it can detect polarity changes on up to 48 I/O lines. Since
36 * this is essentially an (non-PnP) ISA board, I/O Address and IRQ selection
37 * are done through jumpers on the board. You need to pass that information
38 * to this driver as the first and second comedi_config option, respectively.
39 * Note that the 48-channel version uses 16 bytes of IO memory and the 96-
40 * channel version uses 32-bytes (in case you are worried about conflicts).
41 * The 48-channel board is split into two 24-channel comedi subdevices. The
42 * 96-channel board is split into 4 24-channel DIO subdevices.
44 * Note that IRQ support has been added, but it is untested.
46 * To use edge-detection IRQ support, pass the IRQs of both ASICS (for the
47 * 96 channel version) or just 1 ASIC (for 48-channel version). Then, use
48 * comedi_commands with TRIG_NOW. Your callback will be called each time an
49 * edge is triggered, and the data values will be two sample_t's, which
50 * should be concatenated to form one 32-bit unsigned int. This value is
51 * the mask of channels that had edges detected from your channel list. Note
52 * that the bits positions in the mask correspond to positions in your
53 * chanlist when you specified the command and *not* channel id's!
55 * To set the polarity of the edge-detection interrupts pass a nonzero value
56 * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero value for
57 * both CR_RANGE and CR_AREF if you want edge-down polarity.
59 * In the 48-channel version:
61 * On subdev 0, the first 24 channels channels are edge-detect channels.
63 * In the 96-channel board you have the following channels that can do edge
66 * subdev 0, channels 0-24 (first 24 channels of 1st ASIC)
67 * subdev 2, channels 0-24 (first 24 channels of 2nd ASIC)
69 * Configuration Options:
70 * [0] - I/O port base address
71 * [1] - IRQ (for first ASIC, or first 24 channels)
72 * [2] - IRQ (for second ASIC, pcmuio96 only - IRQ for chans 48-72
73 * can be the same as first irq!)
76 #include <linux/module.h>
77 #include <linux/interrupt.h>
78 #include <linux/slab.h>
80 #include "../comedidev.h"
82 #include "comedi_fc.h"
87 * Offset Page 0 Page 1 Page 2 Page 3
88 * ------ ----------- ----------- ----------- -----------
89 * 0x00 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O
90 * 0x01 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O
91 * 0x02 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O
92 * 0x03 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O
93 * 0x04 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O
94 * 0x05 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O
95 * 0x06 INT_PENDING INT_PENDING INT_PENDING INT_PENDING
96 * 0x07 Page/Lock Page/Lock Page/Lock Page/Lock
97 * 0x08 N/A POL_0 ENAB_0 INT_ID0
98 * 0x09 N/A POL_1 ENAB_1 INT_ID1
99 * 0x0a N/A POL_2 ENAB_2 INT_ID2
101 #define PCMUIO_PORT_REG(x) (0x00 + (x))
102 #define PCMUIO_INT_PENDING_REG 0x06
103 #define PCMUIO_PAGE_LOCK_REG 0x07
104 #define PCMUIO_LOCK_PORT(x) ((1 << (x)) & 0x3f)
105 #define PCMUIO_PAGE(x) (((x) & 0x3) << 6)
106 #define PCMUIO_PAGE_MASK PCMUIO_PAGE(3)
107 #define PCMUIO_PAGE_POL 1
108 #define PCMUIO_PAGE_ENAB 2
109 #define PCMUIO_PAGE_INT_ID 3
110 #define PCMUIO_PAGE_REG(x) (0x08 + (x))
112 #define PCMUIO_ASIC_IOSIZE 0x10
113 #define PCMUIO_MAX_ASICS 2
115 struct pcmuio_board {
120 static const struct pcmuio_board pcmuio_boards[] = {
130 struct pcmuio_subdev_private {
131 /* The below is only used for intr subdevices */
133 /* if non-negative, this subdev has an interrupt asic */
136 * subdev-relative channel mask for channels
137 * we are interested in
147 struct pcmuio_private {
151 } asics[PCMUIO_MAX_ASICS];
152 struct pcmuio_subdev_private *sprivs;
155 static void pcmuio_write(struct comedi_device *dev, unsigned int val,
156 int asic, int page, int port)
158 unsigned long iobase = dev->iobase + (asic * PCMUIO_ASIC_IOSIZE);
161 /* Port registers are valid for any page */
162 outb(val & 0xff, iobase + PCMUIO_PORT_REG(port + 0));
163 outb((val >> 8) & 0xff, iobase + PCMUIO_PORT_REG(port + 1));
164 outb((val >> 16) & 0xff, iobase + PCMUIO_PORT_REG(port + 2));
166 outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG);
167 outb(val & 0xff, iobase + PCMUIO_PAGE_REG(0));
168 outb((val >> 8) & 0xff, iobase + PCMUIO_PAGE_REG(1));
169 outb((val >> 16) & 0xff, iobase + PCMUIO_PAGE_REG(2));
173 static unsigned int pcmuio_read(struct comedi_device *dev,
174 int asic, int page, int port)
176 unsigned long iobase = dev->iobase + (asic * PCMUIO_ASIC_IOSIZE);
180 /* Port registers are valid for any page */
181 val = inb(iobase + PCMUIO_PORT_REG(port + 0));
182 val |= (inb(iobase + PCMUIO_PORT_REG(port + 1)) << 8);
183 val |= (inb(iobase + PCMUIO_PORT_REG(port + 2)) << 16);
185 outb(PCMUIO_PAGE(page), iobase + PCMUIO_PAGE_LOCK_REG);
186 val = inb(iobase + PCMUIO_PAGE_REG(0));
187 val |= (inb(iobase + PCMUIO_PAGE_REG(1)) << 8);
188 val |= (inb(iobase + PCMUIO_PAGE_REG(2)) << 16);
195 * Each channel can be individually programmed for input or output.
196 * Writing a '0' to a channel causes the corresponding output pin
197 * to go to a high-z state (pulled high by an external 10K resistor).
198 * This allows it to be used as an input. When used in the input mode,
199 * a read reflects the inverted state of the I/O pin, such that a
200 * high on the pin will read as a '0' in the register. Writing a '1'
201 * to a bit position causes the pin to sink current (up to 12mA),
202 * effectively pulling it low.
204 static int pcmuio_dio_insn_bits(struct comedi_device *dev,
205 struct comedi_subdevice *s,
206 struct comedi_insn *insn, unsigned int *data)
208 unsigned int mask = data[0] & s->io_bits; /* outputs only */
209 unsigned int bits = data[1];
210 int asic = s->index / 2;
211 int port = (s->index % 2) ? 3 : 0;
214 /* get inverted state of the channels from the port */
215 val = pcmuio_read(dev, asic, 0, port);
217 /* get the true state of the channels */
218 s->state = val ^ ((0x1 << s->n_chan) - 1);
222 s->state |= (mask & bits);
224 /* invert the state and update the channels */
225 val = s->state ^ ((0x1 << s->n_chan) - 1);
226 pcmuio_write(dev, val, asic, 0, port);
234 static int pcmuio_dio_insn_config(struct comedi_device *dev,
235 struct comedi_subdevice *s,
236 struct comedi_insn *insn,
239 int asic = s->index / 2;
240 int port = (s->index % 2) ? 3 : 0;
243 ret = comedi_dio_insn_config(dev, s, insn, data, 0);
247 if (data[0] == INSN_CONFIG_DIO_INPUT)
248 pcmuio_write(dev, s->io_bits, asic, 0, port);
253 static void pcmuio_reset(struct comedi_device *dev)
255 const struct pcmuio_board *board = comedi_board(dev);
258 for (asic = 0; asic < board->num_asics; ++asic) {
259 /* first, clear all the DIO port bits */
260 pcmuio_write(dev, 0, asic, 0, 0);
261 pcmuio_write(dev, 0, asic, 0, 3);
263 /* Next, clear all the paged registers for each page */
264 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_POL, 0);
265 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0);
266 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0);
270 static void pcmuio_stop_intr(struct comedi_device *dev,
271 struct comedi_subdevice *s)
273 struct pcmuio_subdev_private *subpriv = s->private;
276 asic = subpriv->intr.asic;
278 return; /* not an interrupt subdev */
280 subpriv->intr.enabled_mask = 0;
281 subpriv->intr.active = 0;
282 s->async->inttrig = NULL;
284 /* disable all intrs for this subdev.. */
285 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_ENAB, 0);
288 static void pcmuio_handle_intr_subdev(struct comedi_device *dev,
289 struct comedi_subdevice *s,
292 struct pcmuio_subdev_private *subpriv = s->private;
293 unsigned int len = s->async->cmd.chanlist_len;
294 unsigned oldevents = s->async->events;
295 unsigned int val = 0;
300 spin_lock_irqsave(&subpriv->intr.spinlock, flags);
302 if (!subpriv->intr.active)
306 mytrig &= ((0x1 << s->n_chan) - 1);
308 if (!(mytrig & subpriv->intr.enabled_mask))
311 for (i = 0; i < len; i++) {
312 unsigned int chan = CR_CHAN(s->async->cmd.chanlist[i]);
313 if (mytrig & (1U << chan))
317 /* Write the scan to the buffer. */
318 if (comedi_buf_put(s->async, ((short *)&val)[0]) &&
319 comedi_buf_put(s->async, ((short *)&val)[1])) {
320 s->async->events |= (COMEDI_CB_BLOCK | COMEDI_CB_EOS);
322 /* Overflow! Stop acquisition!! */
323 /* TODO: STOP_ACQUISITION_CALL_HERE!! */
324 pcmuio_stop_intr(dev, s);
327 /* Check for end of acquisition. */
328 if (!subpriv->intr.continuous) {
329 /* stop_src == TRIG_COUNT */
330 if (subpriv->intr.stop_count > 0) {
331 subpriv->intr.stop_count--;
332 if (subpriv->intr.stop_count == 0) {
333 s->async->events |= COMEDI_CB_EOA;
334 /* TODO: STOP_ACQUISITION_CALL_HERE!! */
335 pcmuio_stop_intr(dev, s);
341 spin_unlock_irqrestore(&subpriv->intr.spinlock, flags);
343 if (oldevents != s->async->events)
344 comedi_event(dev, s);
347 static int pcmuio_handle_asic_interrupt(struct comedi_device *dev, int asic)
349 struct pcmuio_private *devpriv = dev->private;
350 struct pcmuio_subdev_private *subpriv;
351 unsigned long iobase = dev->iobase + (asic * PCMUIO_ASIC_IOSIZE);
352 unsigned int triggered = 0;
355 unsigned char int_pend;
358 spin_lock_irqsave(&devpriv->asics[asic].spinlock, flags);
360 int_pend = inb(iobase + PCMUIO_INT_PENDING_REG) & 0x07;
362 triggered = pcmuio_read(dev, asic, PCMUIO_PAGE_INT_ID, 0);
363 pcmuio_write(dev, 0, asic, PCMUIO_PAGE_INT_ID, 0);
368 spin_unlock_irqrestore(&devpriv->asics[asic].spinlock, flags);
371 struct comedi_subdevice *s;
372 /* TODO here: dispatch io lines to subdevs with commands.. */
373 for (i = 0; i < dev->n_subdevices; i++) {
374 s = &dev->subdevices[i];
375 subpriv = s->private;
376 if (subpriv->intr.asic == asic) {
378 * This is an interrupt subdev, and it
381 pcmuio_handle_intr_subdev(dev, s,
389 static irqreturn_t pcmuio_interrupt(int irq, void *d)
391 struct comedi_device *dev = d;
392 struct pcmuio_private *devpriv = dev->private;
396 for (asic = 0; asic < PCMUIO_MAX_ASICS; ++asic) {
397 if (irq == devpriv->asics[asic].irq) {
398 /* it is an interrupt for ASIC #asic */
399 if (pcmuio_handle_asic_interrupt(dev, asic))
404 return IRQ_NONE; /* interrupt from other source */
408 static int pcmuio_start_intr(struct comedi_device *dev,
409 struct comedi_subdevice *s)
411 struct pcmuio_subdev_private *subpriv = s->private;
413 if (!subpriv->intr.continuous && subpriv->intr.stop_count == 0) {
414 /* An empty acquisition! */
415 s->async->events |= COMEDI_CB_EOA;
416 subpriv->intr.active = 0;
419 unsigned bits = 0, pol_bits = 0, n;
421 struct comedi_cmd *cmd = &s->async->cmd;
423 asic = subpriv->intr.asic;
425 return 1; /* not an interrupt
427 subpriv->intr.enabled_mask = 0;
428 subpriv->intr.active = 1;
430 for (n = 0; n < cmd->chanlist_len; n++) {
431 bits |= (1U << CR_CHAN(cmd->chanlist[n]));
432 pol_bits |= (CR_AREF(cmd->chanlist[n])
434 chanlist[n]) ? 1U : 0U)
435 << CR_CHAN(cmd->chanlist[n]);
438 bits &= ((0x1 << s->n_chan) - 1);
439 subpriv->intr.enabled_mask = bits;
441 /* set pol and enab intrs for this subdev.. */
442 pcmuio_write(dev, pol_bits, asic, PCMUIO_PAGE_POL, 0);
443 pcmuio_write(dev, bits, asic, PCMUIO_PAGE_ENAB, 0);
448 static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s)
450 struct pcmuio_subdev_private *subpriv = s->private;
453 spin_lock_irqsave(&subpriv->intr.spinlock, flags);
454 if (subpriv->intr.active)
455 pcmuio_stop_intr(dev, s);
456 spin_unlock_irqrestore(&subpriv->intr.spinlock, flags);
462 * Internal trigger function to start acquisition for an 'INTERRUPT' subdevice.
465 pcmuio_inttrig_start_intr(struct comedi_device *dev, struct comedi_subdevice *s,
466 unsigned int trignum)
468 struct pcmuio_subdev_private *subpriv = s->private;
475 spin_lock_irqsave(&subpriv->intr.spinlock, flags);
476 s->async->inttrig = NULL;
477 if (subpriv->intr.active)
478 event = pcmuio_start_intr(dev, s);
480 spin_unlock_irqrestore(&subpriv->intr.spinlock, flags);
483 comedi_event(dev, s);
489 * 'do_cmd' function for an 'INTERRUPT' subdevice.
491 static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
493 struct pcmuio_subdev_private *subpriv = s->private;
494 struct comedi_cmd *cmd = &s->async->cmd;
498 spin_lock_irqsave(&subpriv->intr.spinlock, flags);
499 subpriv->intr.active = 1;
501 /* Set up end of acquisition. */
502 switch (cmd->stop_src) {
504 subpriv->intr.continuous = 0;
505 subpriv->intr.stop_count = cmd->stop_arg;
509 subpriv->intr.continuous = 1;
510 subpriv->intr.stop_count = 0;
514 /* Set up start of acquisition. */
515 switch (cmd->start_src) {
517 s->async->inttrig = pcmuio_inttrig_start_intr;
521 event = pcmuio_start_intr(dev, s);
524 spin_unlock_irqrestore(&subpriv->intr.spinlock, flags);
527 comedi_event(dev, s);
532 static int pcmuio_cmdtest(struct comedi_device *dev,
533 struct comedi_subdevice *s,
534 struct comedi_cmd *cmd)
538 /* Step 1 : check if triggers are trivially valid */
540 err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW | TRIG_INT);
541 err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_EXT);
542 err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
543 err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
544 err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
549 /* Step 2a : make sure trigger sources are unique */
551 err |= cfc_check_trigger_is_unique(cmd->start_src);
552 err |= cfc_check_trigger_is_unique(cmd->stop_src);
554 /* Step 2b : and mutually compatible */
559 /* Step 3: check if arguments are trivially valid */
561 err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
562 err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
563 err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
564 err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
566 switch (cmd->stop_src) {
568 /* any count allowed */
571 err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
580 /* step 4: fix up any arguments */
582 /* if (err) return 4; */
587 static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it)
589 const struct pcmuio_board *board = comedi_board(dev);
590 struct comedi_subdevice *s;
591 struct pcmuio_private *devpriv;
592 struct pcmuio_subdev_private *subpriv;
593 int sdev_no, n_subdevs, asic;
594 unsigned int irq[PCMUIO_MAX_ASICS];
597 irq[0] = it->options[1];
598 irq[1] = it->options[2];
600 ret = comedi_request_region(dev, it->options[0],
601 board->num_asics * PCMUIO_ASIC_IOSIZE);
605 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
609 for (asic = 0; asic < PCMUIO_MAX_ASICS; ++asic)
610 spin_lock_init(&devpriv->asics[asic].spinlock);
612 n_subdevs = board->num_asics * 2;
613 devpriv->sprivs = kcalloc(n_subdevs, sizeof(*subpriv), GFP_KERNEL);
614 if (!devpriv->sprivs)
617 ret = comedi_alloc_subdevices(dev, n_subdevs);
621 for (sdev_no = 0; sdev_no < (int)dev->n_subdevices; ++sdev_no) {
622 s = &dev->subdevices[sdev_no];
623 subpriv = &devpriv->sprivs[sdev_no];
624 s->private = subpriv;
626 s->range_table = &range_digital;
627 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
628 s->type = COMEDI_SUBD_DIO;
629 s->insn_bits = pcmuio_dio_insn_bits;
630 s->insn_config = pcmuio_dio_insn_config;
633 /* subdevices 0 and 2 suppport interrupts */
634 if ((sdev_no % 2) == 0) {
635 /* setup the interrupt subdevice */
636 subpriv->intr.asic = sdev_no / 2;
637 dev->read_subdev = s;
638 s->subdev_flags |= SDF_CMD_READ;
639 s->cancel = pcmuio_cancel;
640 s->do_cmd = pcmuio_cmd;
641 s->do_cmdtest = pcmuio_cmdtest;
642 s->len_chanlist = s->n_chan;
644 subpriv->intr.asic = -1;
647 spin_lock_init(&subpriv->intr.spinlock);
652 for (asic = 0; irq[0] && asic < PCMUIO_MAX_ASICS; ++asic) {
654 && request_irq(irq[asic], pcmuio_interrupt,
655 IRQF_SHARED, board->name, dev)) {
657 /* unroll the allocated irqs.. */
658 for (i = asic - 1; i >= 0; --i) {
659 free_irq(irq[i], dev);
660 devpriv->asics[i].irq = irq[i] = 0;
664 devpriv->asics[asic].irq = irq[asic];
670 static void pcmuio_detach(struct comedi_device *dev)
672 struct pcmuio_private *devpriv = dev->private;
676 for (i = 0; i < PCMUIO_MAX_ASICS; ++i) {
677 if (devpriv->asics[i].irq)
678 free_irq(devpriv->asics[i].irq, dev);
680 kfree(devpriv->sprivs);
682 comedi_legacy_detach(dev);
685 static struct comedi_driver pcmuio_driver = {
686 .driver_name = "pcmuio",
687 .module = THIS_MODULE,
688 .attach = pcmuio_attach,
689 .detach = pcmuio_detach,
690 .board_name = &pcmuio_boards[0].name,
691 .offset = sizeof(struct pcmuio_board),
692 .num_names = ARRAY_SIZE(pcmuio_boards),
694 module_comedi_driver(pcmuio_driver);
696 MODULE_AUTHOR("Comedi http://www.comedi.org");
697 MODULE_DESCRIPTION("Comedi low-level driver");
698 MODULE_LICENSE("GPL");