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>
79 #include "../comedidev.h"
81 #include "comedi_fc.h"
86 * Offset Page 0 Page 1 Page 2 Page 3
87 * ------ ----------- ----------- ----------- -----------
88 * 0x00 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O
89 * 0x01 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O
90 * 0x02 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O
91 * 0x03 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O
92 * 0x04 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O
93 * 0x05 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O
94 * 0x06 INT_PENDING INT_PENDING INT_PENDING INT_PENDING
95 * 0x07 Page/Lock Page/Lock Page/Lock Page/Lock
96 * 0x08 N/A POL_0 ENAB_0 INT_ID0
97 * 0x09 N/A POL_1 ENAB_1 INT_ID1
98 * 0x0a N/A POL_2 ENAB_2 INT_ID2
100 #define PCMUIO_PORT_REG(x) (0x00 + (x))
101 #define PCMUIO_INT_PENDING_REG 0x06
102 #define PCMUIO_PAGE_LOCK_REG 0x07
103 #define PCMUIO_LOCK_PORT(x) ((1 << (x)) & 0x3f)
104 #define PCMUIO_PAGE(x) (((x) & 0x3) << 6)
105 #define PCMUIO_PAGE_MASK PCMUIO_PAGE(3)
106 #define PCMUIO_PAGE_POL 1
107 #define PCMUIO_PAGE_ENAB 2
108 #define PCMUIO_PAGE_INT_ID 3
109 #define PCMUIO_PAGE_REG(x) (0x08 + (x))
111 #define PCMUIO_ASIC_IOSIZE 0x10
112 #define PCMUIO_MAX_ASICS 2
114 struct pcmuio_board
{
119 static const struct pcmuio_board pcmuio_boards
[] = {
130 spinlock_t pagelock
; /* protects the page registers */
131 spinlock_t spinlock
; /* protects member variables */
132 unsigned int enabled_mask
;
133 unsigned int stop_count
;
134 unsigned int active
:1;
137 struct pcmuio_private
{
138 struct pcmuio_asic asics
[PCMUIO_MAX_ASICS
];
142 static inline unsigned long pcmuio_asic_iobase(struct comedi_device
*dev
,
145 return dev
->iobase
+ (asic
* PCMUIO_ASIC_IOSIZE
);
148 static inline int pcmuio_subdevice_to_asic(struct comedi_subdevice
*s
)
151 * subdevice 0 and 1 are handled by the first asic
152 * subdevice 2 and 3 are handled by the second asic
157 static inline int pcmuio_subdevice_to_port(struct comedi_subdevice
*s
)
160 * subdevice 0 and 2 use port registers 0-2
161 * subdevice 1 and 3 use port registers 3-5
163 return (s
->index
% 2) ? 3 : 0;
166 static void pcmuio_write(struct comedi_device
*dev
, unsigned int val
,
167 int asic
, int page
, int port
)
169 struct pcmuio_private
*devpriv
= dev
->private;
170 struct pcmuio_asic
*chip
= &devpriv
->asics
[asic
];
171 unsigned long iobase
= pcmuio_asic_iobase(dev
, asic
);
174 spin_lock_irqsave(&chip
->pagelock
, flags
);
176 /* Port registers are valid for any page */
177 outb(val
& 0xff, iobase
+ PCMUIO_PORT_REG(port
+ 0));
178 outb((val
>> 8) & 0xff, iobase
+ PCMUIO_PORT_REG(port
+ 1));
179 outb((val
>> 16) & 0xff, iobase
+ PCMUIO_PORT_REG(port
+ 2));
181 outb(PCMUIO_PAGE(page
), iobase
+ PCMUIO_PAGE_LOCK_REG
);
182 outb(val
& 0xff, iobase
+ PCMUIO_PAGE_REG(0));
183 outb((val
>> 8) & 0xff, iobase
+ PCMUIO_PAGE_REG(1));
184 outb((val
>> 16) & 0xff, iobase
+ PCMUIO_PAGE_REG(2));
186 spin_unlock_irqrestore(&chip
->pagelock
, flags
);
189 static unsigned int pcmuio_read(struct comedi_device
*dev
,
190 int asic
, int page
, int port
)
192 struct pcmuio_private
*devpriv
= dev
->private;
193 struct pcmuio_asic
*chip
= &devpriv
->asics
[asic
];
194 unsigned long iobase
= pcmuio_asic_iobase(dev
, asic
);
198 spin_lock_irqsave(&chip
->pagelock
, flags
);
200 /* Port registers are valid for any page */
201 val
= inb(iobase
+ PCMUIO_PORT_REG(port
+ 0));
202 val
|= (inb(iobase
+ PCMUIO_PORT_REG(port
+ 1)) << 8);
203 val
|= (inb(iobase
+ PCMUIO_PORT_REG(port
+ 2)) << 16);
205 outb(PCMUIO_PAGE(page
), iobase
+ PCMUIO_PAGE_LOCK_REG
);
206 val
= inb(iobase
+ PCMUIO_PAGE_REG(0));
207 val
|= (inb(iobase
+ PCMUIO_PAGE_REG(1)) << 8);
208 val
|= (inb(iobase
+ PCMUIO_PAGE_REG(2)) << 16);
210 spin_unlock_irqrestore(&chip
->pagelock
, flags
);
216 * Each channel can be individually programmed for input or output.
217 * Writing a '0' to a channel causes the corresponding output pin
218 * to go to a high-z state (pulled high by an external 10K resistor).
219 * This allows it to be used as an input. When used in the input mode,
220 * a read reflects the inverted state of the I/O pin, such that a
221 * high on the pin will read as a '0' in the register. Writing a '1'
222 * to a bit position causes the pin to sink current (up to 12mA),
223 * effectively pulling it low.
225 static int pcmuio_dio_insn_bits(struct comedi_device
*dev
,
226 struct comedi_subdevice
*s
,
227 struct comedi_insn
*insn
,
230 int asic
= pcmuio_subdevice_to_asic(s
);
231 int port
= pcmuio_subdevice_to_port(s
);
232 unsigned int chanmask
= (1 << s
->n_chan
) - 1;
236 mask
= comedi_dio_update_state(s
, data
);
239 * Outputs are inverted, invert the state and
240 * update the channels.
242 * The s->io_bits mask makes sure the input channels
243 * are '0' so that the outputs pins stay in a high
246 val
= ~s
->state
& chanmask
;
248 pcmuio_write(dev
, val
, asic
, 0, port
);
251 /* get inverted state of the channels from the port */
252 val
= pcmuio_read(dev
, asic
, 0, port
);
254 /* return the true state of the channels */
255 data
[1] = ~val
& chanmask
;
260 static int pcmuio_dio_insn_config(struct comedi_device
*dev
,
261 struct comedi_subdevice
*s
,
262 struct comedi_insn
*insn
,
265 int asic
= pcmuio_subdevice_to_asic(s
);
266 int port
= pcmuio_subdevice_to_port(s
);
269 ret
= comedi_dio_insn_config(dev
, s
, insn
, data
, 0);
273 if (data
[0] == INSN_CONFIG_DIO_INPUT
)
274 pcmuio_write(dev
, s
->io_bits
, asic
, 0, port
);
279 static void pcmuio_reset(struct comedi_device
*dev
)
281 const struct pcmuio_board
*board
= dev
->board_ptr
;
284 for (asic
= 0; asic
< board
->num_asics
; ++asic
) {
285 /* first, clear all the DIO port bits */
286 pcmuio_write(dev
, 0, asic
, 0, 0);
287 pcmuio_write(dev
, 0, asic
, 0, 3);
289 /* Next, clear all the paged registers for each page */
290 pcmuio_write(dev
, 0, asic
, PCMUIO_PAGE_POL
, 0);
291 pcmuio_write(dev
, 0, asic
, PCMUIO_PAGE_ENAB
, 0);
292 pcmuio_write(dev
, 0, asic
, PCMUIO_PAGE_INT_ID
, 0);
296 /* chip->spinlock is already locked */
297 static void pcmuio_stop_intr(struct comedi_device
*dev
,
298 struct comedi_subdevice
*s
)
300 struct pcmuio_private
*devpriv
= dev
->private;
301 int asic
= pcmuio_subdevice_to_asic(s
);
302 struct pcmuio_asic
*chip
= &devpriv
->asics
[asic
];
304 chip
->enabled_mask
= 0;
306 s
->async
->inttrig
= NULL
;
308 /* disable all intrs for this subdev.. */
309 pcmuio_write(dev
, 0, asic
, PCMUIO_PAGE_ENAB
, 0);
312 static void pcmuio_handle_intr_subdev(struct comedi_device
*dev
,
313 struct comedi_subdevice
*s
,
316 struct pcmuio_private
*devpriv
= dev
->private;
317 int asic
= pcmuio_subdevice_to_asic(s
);
318 struct pcmuio_asic
*chip
= &devpriv
->asics
[asic
];
319 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
320 unsigned oldevents
= s
->async
->events
;
321 unsigned int val
= 0;
325 spin_lock_irqsave(&chip
->spinlock
, flags
);
330 if (!(triggered
& chip
->enabled_mask
))
333 for (i
= 0; i
< cmd
->chanlist_len
; i
++) {
334 unsigned int chan
= CR_CHAN(cmd
->chanlist
[i
]);
336 if (triggered
& (1 << chan
))
340 /* Write the scan to the buffer. */
341 if (comedi_buf_put(s
, val
) &&
342 comedi_buf_put(s
, val
>> 16)) {
343 s
->async
->events
|= (COMEDI_CB_BLOCK
| COMEDI_CB_EOS
);
345 /* Overflow! Stop acquisition!! */
346 /* TODO: STOP_ACQUISITION_CALL_HERE!! */
347 pcmuio_stop_intr(dev
, s
);
350 /* Check for end of acquisition. */
351 if (cmd
->stop_src
== TRIG_COUNT
) {
352 if (chip
->stop_count
> 0) {
354 if (chip
->stop_count
== 0) {
355 s
->async
->events
|= COMEDI_CB_EOA
;
356 /* TODO: STOP_ACQUISITION_CALL_HERE!! */
357 pcmuio_stop_intr(dev
, s
);
363 spin_unlock_irqrestore(&chip
->spinlock
, flags
);
365 if (oldevents
!= s
->async
->events
)
366 comedi_event(dev
, s
);
369 static int pcmuio_handle_asic_interrupt(struct comedi_device
*dev
, int asic
)
371 /* there are could be two asics so we can't use dev->read_subdev */
372 struct comedi_subdevice
*s
= &dev
->subdevices
[asic
* 2];
373 unsigned long iobase
= pcmuio_asic_iobase(dev
, asic
);
376 /* are there any interrupts pending */
377 val
= inb(iobase
+ PCMUIO_INT_PENDING_REG
) & 0x07;
381 /* get, and clear, the pending interrupts */
382 val
= pcmuio_read(dev
, asic
, PCMUIO_PAGE_INT_ID
, 0);
383 pcmuio_write(dev
, 0, asic
, PCMUIO_PAGE_INT_ID
, 0);
385 /* handle the pending interrupts */
386 pcmuio_handle_intr_subdev(dev
, s
, val
);
391 static irqreturn_t
pcmuio_interrupt(int irq
, void *d
)
393 struct comedi_device
*dev
= d
;
394 struct pcmuio_private
*devpriv
= dev
->private;
398 handled
+= pcmuio_handle_asic_interrupt(dev
, 0);
399 if (irq
== devpriv
->irq2
)
400 handled
+= pcmuio_handle_asic_interrupt(dev
, 1);
402 return handled
? IRQ_HANDLED
: IRQ_NONE
;
405 /* chip->spinlock is already locked */
406 static void pcmuio_start_intr(struct comedi_device
*dev
,
407 struct comedi_subdevice
*s
)
409 struct pcmuio_private
*devpriv
= dev
->private;
410 int asic
= pcmuio_subdevice_to_asic(s
);
411 struct pcmuio_asic
*chip
= &devpriv
->asics
[asic
];
412 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
413 unsigned int bits
= 0;
414 unsigned int pol_bits
= 0;
417 chip
->enabled_mask
= 0;
420 for (i
= 0; i
< cmd
->chanlist_len
; i
++) {
421 unsigned int chanspec
= cmd
->chanlist
[i
];
422 unsigned int chan
= CR_CHAN(chanspec
);
423 unsigned int range
= CR_RANGE(chanspec
);
424 unsigned int aref
= CR_AREF(chanspec
);
427 pol_bits
|= ((aref
|| range
) ? 1 : 0) << chan
;
430 bits
&= ((1 << s
->n_chan
) - 1);
431 chip
->enabled_mask
= bits
;
433 /* set pol and enab intrs for this subdev.. */
434 pcmuio_write(dev
, pol_bits
, asic
, PCMUIO_PAGE_POL
, 0);
435 pcmuio_write(dev
, bits
, asic
, PCMUIO_PAGE_ENAB
, 0);
438 static int pcmuio_cancel(struct comedi_device
*dev
, struct comedi_subdevice
*s
)
440 struct pcmuio_private
*devpriv
= dev
->private;
441 int asic
= pcmuio_subdevice_to_asic(s
);
442 struct pcmuio_asic
*chip
= &devpriv
->asics
[asic
];
445 spin_lock_irqsave(&chip
->spinlock
, flags
);
447 pcmuio_stop_intr(dev
, s
);
448 spin_unlock_irqrestore(&chip
->spinlock
, flags
);
453 static int pcmuio_inttrig_start_intr(struct comedi_device
*dev
,
454 struct comedi_subdevice
*s
,
455 unsigned int trig_num
)
457 struct pcmuio_private
*devpriv
= dev
->private;
458 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
459 int asic
= pcmuio_subdevice_to_asic(s
);
460 struct pcmuio_asic
*chip
= &devpriv
->asics
[asic
];
463 if (trig_num
!= cmd
->start_arg
)
466 spin_lock_irqsave(&chip
->spinlock
, flags
);
467 s
->async
->inttrig
= NULL
;
469 pcmuio_start_intr(dev
, s
);
471 spin_unlock_irqrestore(&chip
->spinlock
, flags
);
477 * 'do_cmd' function for an 'INTERRUPT' subdevice.
479 static int pcmuio_cmd(struct comedi_device
*dev
, struct comedi_subdevice
*s
)
481 struct pcmuio_private
*devpriv
= dev
->private;
482 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
483 int asic
= pcmuio_subdevice_to_asic(s
);
484 struct pcmuio_asic
*chip
= &devpriv
->asics
[asic
];
487 spin_lock_irqsave(&chip
->spinlock
, flags
);
490 chip
->stop_count
= cmd
->stop_arg
;
492 /* Set up start of acquisition. */
493 if (cmd
->start_src
== TRIG_INT
)
494 s
->async
->inttrig
= pcmuio_inttrig_start_intr
;
496 pcmuio_start_intr(dev
, s
);
498 spin_unlock_irqrestore(&chip
->spinlock
, flags
);
503 static int pcmuio_cmdtest(struct comedi_device
*dev
,
504 struct comedi_subdevice
*s
,
505 struct comedi_cmd
*cmd
)
509 /* Step 1 : check if triggers are trivially valid */
511 err
|= cfc_check_trigger_src(&cmd
->start_src
, TRIG_NOW
| TRIG_INT
);
512 err
|= cfc_check_trigger_src(&cmd
->scan_begin_src
, TRIG_EXT
);
513 err
|= cfc_check_trigger_src(&cmd
->convert_src
, TRIG_NOW
);
514 err
|= cfc_check_trigger_src(&cmd
->scan_end_src
, TRIG_COUNT
);
515 err
|= cfc_check_trigger_src(&cmd
->stop_src
, TRIG_COUNT
| TRIG_NONE
);
520 /* Step 2a : make sure trigger sources are unique */
522 err
|= cfc_check_trigger_is_unique(cmd
->start_src
);
523 err
|= cfc_check_trigger_is_unique(cmd
->stop_src
);
525 /* Step 2b : and mutually compatible */
530 /* Step 3: check if arguments are trivially valid */
532 err
|= cfc_check_trigger_arg_is(&cmd
->start_arg
, 0);
533 err
|= cfc_check_trigger_arg_is(&cmd
->scan_begin_arg
, 0);
534 err
|= cfc_check_trigger_arg_is(&cmd
->convert_arg
, 0);
535 err
|= cfc_check_trigger_arg_is(&cmd
->scan_end_arg
, cmd
->chanlist_len
);
537 if (cmd
->stop_src
== TRIG_COUNT
)
538 err
|= cfc_check_trigger_arg_min(&cmd
->stop_arg
, 1);
540 err
|= cfc_check_trigger_arg_is(&cmd
->stop_arg
, 0);
545 /* step 4: fix up any arguments */
547 /* if (err) return 4; */
552 static int pcmuio_attach(struct comedi_device
*dev
, struct comedi_devconfig
*it
)
554 const struct pcmuio_board
*board
= dev
->board_ptr
;
555 struct comedi_subdevice
*s
;
556 struct pcmuio_private
*devpriv
;
560 ret
= comedi_request_region(dev
, it
->options
[0],
561 board
->num_asics
* PCMUIO_ASIC_IOSIZE
);
565 devpriv
= comedi_alloc_devpriv(dev
, sizeof(*devpriv
));
569 for (i
= 0; i
< PCMUIO_MAX_ASICS
; ++i
) {
570 struct pcmuio_asic
*chip
= &devpriv
->asics
[i
];
572 spin_lock_init(&chip
->pagelock
);
573 spin_lock_init(&chip
->spinlock
);
578 if (it
->options
[1]) {
579 /* request the irq for the 1st asic */
580 ret
= request_irq(it
->options
[1], pcmuio_interrupt
, 0,
581 dev
->board_name
, dev
);
583 dev
->irq
= it
->options
[1];
586 if (board
->num_asics
== 2) {
587 if (it
->options
[2] == dev
->irq
) {
588 /* the same irq (or none) is used by both asics */
589 devpriv
->irq2
= it
->options
[2];
590 } else if (it
->options
[2]) {
591 /* request the irq for the 2nd asic */
592 ret
= request_irq(it
->options
[2], pcmuio_interrupt
, 0,
593 dev
->board_name
, dev
);
595 devpriv
->irq2
= it
->options
[2];
599 ret
= comedi_alloc_subdevices(dev
, board
->num_asics
* 2);
603 for (i
= 0; i
< dev
->n_subdevices
; ++i
) {
604 s
= &dev
->subdevices
[i
];
605 s
->type
= COMEDI_SUBD_DIO
;
606 s
->subdev_flags
= SDF_READABLE
| SDF_WRITABLE
;
609 s
->range_table
= &range_digital
;
610 s
->insn_bits
= pcmuio_dio_insn_bits
;
611 s
->insn_config
= pcmuio_dio_insn_config
;
613 /* subdevices 0 and 2 can suppport interrupts */
614 if ((i
== 0 && dev
->irq
) || (i
== 2 && devpriv
->irq2
)) {
615 /* setup the interrupt subdevice */
616 dev
->read_subdev
= s
;
617 s
->subdev_flags
|= SDF_CMD_READ
;
618 s
->len_chanlist
= s
->n_chan
;
619 s
->cancel
= pcmuio_cancel
;
620 s
->do_cmd
= pcmuio_cmd
;
621 s
->do_cmdtest
= pcmuio_cmdtest
;
628 static void pcmuio_detach(struct comedi_device
*dev
)
630 struct pcmuio_private
*devpriv
= dev
->private;
635 /* free the 2nd irq if used, the core will free the 1st one */
636 if (devpriv
->irq2
&& devpriv
->irq2
!= dev
->irq
)
637 free_irq(devpriv
->irq2
, dev
);
639 comedi_legacy_detach(dev
);
642 static struct comedi_driver pcmuio_driver
= {
643 .driver_name
= "pcmuio",
644 .module
= THIS_MODULE
,
645 .attach
= pcmuio_attach
,
646 .detach
= pcmuio_detach
,
647 .board_name
= &pcmuio_boards
[0].name
,
648 .offset
= sizeof(struct pcmuio_board
),
649 .num_names
= ARRAY_SIZE(pcmuio_boards
),
651 module_comedi_driver(pcmuio_driver
);
653 MODULE_AUTHOR("Comedi http://www.comedi.org");
654 MODULE_DESCRIPTION("Comedi low-level driver");
655 MODULE_LICENSE("GPL");