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 | |