// SPDX-License-Identifier: GPL-2.0+ /* * pcmuio.c * Comedi driver for Winsystems PC-104 based 48/96-channel DIO boards. * * COMEDI - Linux Control and Measurement Device Interface * Copyright (C) 2006 Calin A. Culianu <[email protected]> */ /* * Driver: pcmuio * Description: Winsystems PC-104 based 48/96-channel DIO boards. * Devices: [Winsystems] PCM-UIO48A (pcmuio48), PCM-UIO96A (pcmuio96) * Author: Calin Culianu <[email protected]> * Updated: Fri, 13 Jan 2006 12:01:01 -0500 * Status: works * * A driver for the relatively straightforward-to-program PCM-UIO48A and * PCM-UIO96A boards from Winsystems. These boards use either one or two * (in the 96-DIO version) WS16C48 ASIC HighDensity I/O Chips (HDIO). This * chip is interesting in that each I/O line is individually programmable * for INPUT or OUTPUT (thus comedi_dio_config can be done on a per-channel * basis). Also, each chip supports edge-triggered interrupts for the first * 24 I/O lines. Of course, since the 96-channel version of the board has * two ASICs, it can detect polarity changes on up to 48 I/O lines. Since * this is essentially an (non-PnP) ISA board, I/O Address and IRQ selection * are done through jumpers on the board. You need to pass that information * to this driver as the first and second comedi_config option, respectively. * Note that the 48-channel version uses 16 bytes of IO memory and the 96- * channel version uses 32-bytes (in case you are worried about conflicts). * The 48-channel board is split into two 24-channel comedi subdevices. The * 96-channel board is split into 4 24-channel DIO subdevices. * * Note that IRQ support has been added, but it is untested. * * To use edge-detection IRQ support, pass the IRQs of both ASICS (for the * 96 channel version) or just 1 ASIC (for 48-channel version). Then, use * comedi_commands with TRIG_NOW. Your callback will be called each time an * edge is triggered, and the data values will be two sample_t's, which * should be concatenated to form one 32-bit unsigned int. This value is * the mask of channels that had edges detected from your channel list. Note * that the bits positions in the mask correspond to positions in your * chanlist when you specified the command and *not* channel id's! * * To set the polarity of the edge-detection interrupts pass a nonzero value * for either CR_RANGE or CR_AREF for edge-up polarity, or a zero value for * both CR_RANGE and CR_AREF if you want edge-down polarity. * * In the 48-channel version: * * On subdev 0, the first 24 channels are edge-detect channels. * * In the 96-channel board you have the following channels that can do edge * detection: * * subdev 0, channels 0-24 (first 24 channels of 1st ASIC) * subdev 2, channels 0-24 (first 24 channels of 2nd ASIC) * * Configuration Options: * [0] - I/O port base address * [1] - IRQ (for first ASIC, or first 24 channels) * [2] - IRQ (for second ASIC, pcmuio96 only - IRQ for chans 48-72 * can be the same as first irq!) */ #include <linux/module.h> #include <linux/interrupt.h> #include <linux/comedi/comedidev.h> /* * Register I/O map * * Offset Page 0 Page 1 Page 2 Page 3 * ------ ----------- ----------- ----------- ----------- * 0x00 Port 0 I/O Port 0 I/O Port 0 I/O Port 0 I/O * 0x01 Port 1 I/O Port 1 I/O Port 1 I/O Port 1 I/O * 0x02 Port 2 I/O Port 2 I/O Port 2 I/O Port 2 I/O * 0x03 Port 3 I/O Port 3 I/O Port 3 I/O Port 3 I/O * 0x04 Port 4 I/O Port 4 I/O Port 4 I/O Port 4 I/O * 0x05 Port 5 I/O Port 5 I/O Port 5 I/O Port 5 I/O * 0x06 INT_PENDING INT_PENDING INT_PENDING INT_PENDING * 0x07 Page/Lock Page/Lock Page/Lock Page/Lock * 0x08 N/A POL_0 ENAB_0 INT_ID0 * 0x09 N/A POL_1 ENAB_1 INT_ID1 * 0x0a N/A POL_2 ENAB_2 INT_ID2 */ #define PCMUIO_PORT_REG(x) … #define PCMUIO_INT_PENDING_REG … #define PCMUIO_PAGE_LOCK_REG … #define PCMUIO_LOCK_PORT(x) … #define PCMUIO_PAGE(x) … #define PCMUIO_PAGE_MASK … #define PCMUIO_PAGE_POL … #define PCMUIO_PAGE_ENAB … #define PCMUIO_PAGE_INT_ID … #define PCMUIO_PAGE_REG(x) … #define PCMUIO_ASIC_IOSIZE … #define PCMUIO_MAX_ASICS … struct pcmuio_board { … }; static const struct pcmuio_board pcmuio_boards[] = …; struct pcmuio_asic { … }; struct pcmuio_private { … }; static inline unsigned long pcmuio_asic_iobase(struct comedi_device *dev, int asic) { … } static inline int pcmuio_subdevice_to_asic(struct comedi_subdevice *s) { … } static inline int pcmuio_subdevice_to_port(struct comedi_subdevice *s) { … } static void pcmuio_write(struct comedi_device *dev, unsigned int val, int asic, int page, int port) { … } static unsigned int pcmuio_read(struct comedi_device *dev, int asic, int page, int port) { … } /* * Each channel can be individually programmed for input or output. * Writing a '0' to a channel causes the corresponding output pin * to go to a high-z state (pulled high by an external 10K resistor). * This allows it to be used as an input. When used in the input mode, * a read reflects the inverted state of the I/O pin, such that a * high on the pin will read as a '0' in the register. Writing a '1' * to a bit position causes the pin to sink current (up to 12mA), * effectively pulling it low. */ static int pcmuio_dio_insn_bits(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { … } static int pcmuio_dio_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsigned int *data) { … } static void pcmuio_reset(struct comedi_device *dev) { … } /* chip->spinlock is already locked */ static void pcmuio_stop_intr(struct comedi_device *dev, struct comedi_subdevice *s) { … } static void pcmuio_handle_intr_subdev(struct comedi_device *dev, struct comedi_subdevice *s, unsigned int triggered) { … } static int pcmuio_handle_asic_interrupt(struct comedi_device *dev, int asic) { … } static irqreturn_t pcmuio_interrupt(int irq, void *d) { … } /* chip->spinlock is already locked */ static void pcmuio_start_intr(struct comedi_device *dev, struct comedi_subdevice *s) { … } static int pcmuio_cancel(struct comedi_device *dev, struct comedi_subdevice *s) { … } static int pcmuio_inttrig_start_intr(struct comedi_device *dev, struct comedi_subdevice *s, unsigned int trig_num) { … } /* * 'do_cmd' function for an 'INTERRUPT' subdevice. */ static int pcmuio_cmd(struct comedi_device *dev, struct comedi_subdevice *s) { … } static int pcmuio_cmdtest(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_cmd *cmd) { … } static int pcmuio_attach(struct comedi_device *dev, struct comedi_devconfig *it) { … } static void pcmuio_detach(struct comedi_device *dev) { … } static struct comedi_driver pcmuio_driver = …; module_comedi_driver(…); MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …; MODULE_LICENSE(…) …;