1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * aio_iiro_16.c |
4 | * Comedi driver for Access I/O Products 104-IIRO-16 board |
5 | * Copyright (C) 2006 C&C Technologies, Inc. |
6 | */ |
7 | |
8 | /* |
9 | * Driver: aio_iiro_16 |
10 | * Description: Access I/O Products PC/104 Isolated Input/Relay Output Board |
11 | * Author: Zachary Ware <zach.ware@cctechnol.com> |
12 | * Devices: [Access I/O] 104-IIRO-16 (aio_iiro_16) |
13 | * Status: experimental |
14 | * |
15 | * Configuration Options: |
16 | * [0] - I/O port base address |
17 | * [1] - IRQ (optional) |
18 | * |
19 | * The board supports interrupts on change of state of the digital inputs. |
20 | * The sample data returned by the async command indicates which inputs |
21 | * changed state and the current state of the inputs: |
22 | * |
23 | * Bit 23 - IRQ Enable (1) / Disable (0) |
24 | * Bit 17 - Input 8-15 Changed State (1 = Changed, 0 = No Change) |
25 | * Bit 16 - Input 0-7 Changed State (1 = Changed, 0 = No Change) |
26 | * Bit 15 - Digital input 15 |
27 | * ... |
28 | * Bit 0 - Digital input 0 |
29 | */ |
30 | |
31 | #include <linux/module.h> |
32 | #include <linux/interrupt.h> |
33 | #include <linux/comedi/comedidev.h> |
34 | |
35 | #define AIO_IIRO_16_RELAY_0_7 0x00 |
36 | #define AIO_IIRO_16_INPUT_0_7 0x01 |
37 | #define AIO_IIRO_16_IRQ 0x02 |
38 | #define AIO_IIRO_16_RELAY_8_15 0x04 |
39 | #define AIO_IIRO_16_INPUT_8_15 0x05 |
40 | #define AIO_IIRO_16_STATUS 0x07 |
41 | #define AIO_IIRO_16_STATUS_IRQE BIT(7) |
42 | #define AIO_IIRO_16_STATUS_INPUT_8_15 BIT(1) |
43 | #define AIO_IIRO_16_STATUS_INPUT_0_7 BIT(0) |
44 | |
45 | static unsigned int aio_iiro_16_read_inputs(struct comedi_device *dev) |
46 | { |
47 | unsigned int val; |
48 | |
49 | val = inb(port: dev->iobase + AIO_IIRO_16_INPUT_0_7); |
50 | val |= inb(port: dev->iobase + AIO_IIRO_16_INPUT_8_15) << 8; |
51 | |
52 | return val; |
53 | } |
54 | |
55 | static irqreturn_t aio_iiro_16_cos(int irq, void *d) |
56 | { |
57 | struct comedi_device *dev = d; |
58 | struct comedi_subdevice *s = dev->read_subdev; |
59 | unsigned int status; |
60 | unsigned int val; |
61 | |
62 | status = inb(port: dev->iobase + AIO_IIRO_16_STATUS); |
63 | if (!(status & AIO_IIRO_16_STATUS_IRQE)) |
64 | return IRQ_NONE; |
65 | |
66 | val = aio_iiro_16_read_inputs(dev); |
67 | val |= (status << 16); |
68 | |
69 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
70 | comedi_handle_events(dev, s); |
71 | |
72 | return IRQ_HANDLED; |
73 | } |
74 | |
75 | static void aio_iiro_enable_irq(struct comedi_device *dev, bool enable) |
76 | { |
77 | if (enable) |
78 | inb(port: dev->iobase + AIO_IIRO_16_IRQ); |
79 | else |
80 | outb(value: 0, port: dev->iobase + AIO_IIRO_16_IRQ); |
81 | } |
82 | |
83 | static int aio_iiro_16_cos_cancel(struct comedi_device *dev, |
84 | struct comedi_subdevice *s) |
85 | { |
86 | aio_iiro_enable_irq(dev, enable: false); |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static int aio_iiro_16_cos_cmd(struct comedi_device *dev, |
92 | struct comedi_subdevice *s) |
93 | { |
94 | aio_iiro_enable_irq(dev, enable: true); |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | static int aio_iiro_16_cos_cmdtest(struct comedi_device *dev, |
100 | struct comedi_subdevice *s, |
101 | struct comedi_cmd *cmd) |
102 | { |
103 | int err = 0; |
104 | |
105 | /* Step 1 : check if triggers are trivially valid */ |
106 | |
107 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
108 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_EXT); |
109 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_FOLLOW); |
110 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
111 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_NONE); |
112 | |
113 | if (err) |
114 | return 1; |
115 | |
116 | /* Step 2a : make sure trigger sources are unique */ |
117 | /* Step 2b : and mutually compatible */ |
118 | |
119 | /* Step 3: check if arguments are trivially valid */ |
120 | |
121 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
122 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
123 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
124 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
125 | val: cmd->chanlist_len); |
126 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
127 | |
128 | if (err) |
129 | return 3; |
130 | |
131 | /* Step 4: fix up any arguments */ |
132 | |
133 | /* Step 5: check channel list if it exists */ |
134 | |
135 | return 0; |
136 | } |
137 | |
138 | static int aio_iiro_16_do_insn_bits(struct comedi_device *dev, |
139 | struct comedi_subdevice *s, |
140 | struct comedi_insn *insn, |
141 | unsigned int *data) |
142 | { |
143 | if (comedi_dio_update_state(s, data)) { |
144 | outb(value: s->state & 0xff, port: dev->iobase + AIO_IIRO_16_RELAY_0_7); |
145 | outb(value: (s->state >> 8) & 0xff, |
146 | port: dev->iobase + AIO_IIRO_16_RELAY_8_15); |
147 | } |
148 | |
149 | data[1] = s->state; |
150 | |
151 | return insn->n; |
152 | } |
153 | |
154 | static int aio_iiro_16_di_insn_bits(struct comedi_device *dev, |
155 | struct comedi_subdevice *s, |
156 | struct comedi_insn *insn, |
157 | unsigned int *data) |
158 | { |
159 | data[1] = aio_iiro_16_read_inputs(dev); |
160 | |
161 | return insn->n; |
162 | } |
163 | |
164 | static int aio_iiro_16_attach(struct comedi_device *dev, |
165 | struct comedi_devconfig *it) |
166 | { |
167 | struct comedi_subdevice *s; |
168 | int ret; |
169 | |
170 | ret = comedi_request_region(dev, start: it->options[0], len: 0x8); |
171 | if (ret) |
172 | return ret; |
173 | |
174 | aio_iiro_enable_irq(dev, enable: false); |
175 | |
176 | /* |
177 | * Digital input change of state interrupts are optionally supported |
178 | * using IRQ 2-7, 10-12, 14, or 15. |
179 | */ |
180 | if ((1 << it->options[1]) & 0xdcfc) { |
181 | ret = request_irq(irq: it->options[1], handler: aio_iiro_16_cos, flags: 0, |
182 | name: dev->board_name, dev); |
183 | if (ret == 0) |
184 | dev->irq = it->options[1]; |
185 | } |
186 | |
187 | ret = comedi_alloc_subdevices(dev, num_subdevices: 2); |
188 | if (ret) |
189 | return ret; |
190 | |
191 | /* Digital Output subdevice */ |
192 | s = &dev->subdevices[0]; |
193 | s->type = COMEDI_SUBD_DO; |
194 | s->subdev_flags = SDF_WRITABLE; |
195 | s->n_chan = 16; |
196 | s->maxdata = 1; |
197 | s->range_table = &range_digital; |
198 | s->insn_bits = aio_iiro_16_do_insn_bits; |
199 | |
200 | /* get the initial state of the relays */ |
201 | s->state = inb(port: dev->iobase + AIO_IIRO_16_RELAY_0_7) | |
202 | (inb(port: dev->iobase + AIO_IIRO_16_RELAY_8_15) << 8); |
203 | |
204 | /* Digital Input subdevice */ |
205 | s = &dev->subdevices[1]; |
206 | s->type = COMEDI_SUBD_DI; |
207 | s->subdev_flags = SDF_READABLE; |
208 | s->n_chan = 16; |
209 | s->maxdata = 1; |
210 | s->range_table = &range_digital; |
211 | s->insn_bits = aio_iiro_16_di_insn_bits; |
212 | if (dev->irq) { |
213 | dev->read_subdev = s; |
214 | s->subdev_flags |= SDF_CMD_READ | SDF_LSAMPL; |
215 | s->len_chanlist = 1; |
216 | s->do_cmdtest = aio_iiro_16_cos_cmdtest; |
217 | s->do_cmd = aio_iiro_16_cos_cmd; |
218 | s->cancel = aio_iiro_16_cos_cancel; |
219 | } |
220 | |
221 | return 0; |
222 | } |
223 | |
224 | static struct comedi_driver aio_iiro_16_driver = { |
225 | .driver_name = "aio_iiro_16" , |
226 | .module = THIS_MODULE, |
227 | .attach = aio_iiro_16_attach, |
228 | .detach = comedi_legacy_detach, |
229 | }; |
230 | module_comedi_driver(aio_iiro_16_driver); |
231 | |
232 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
233 | MODULE_DESCRIPTION("Comedi driver for Access I/O Products 104-IIRO-16 board" ); |
234 | MODULE_LICENSE("GPL" ); |
235 | |