1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * comedi/drivers/amplc_pc236_common.c |
4 | * Common support code for "amplc_pc236" and "amplc_pci236". |
5 | * |
6 | * Copyright (C) 2002-2014 MEV Ltd. <https://www.mev.co.uk/> |
7 | * |
8 | * COMEDI - Linux Control and Measurement Device Interface |
9 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
10 | */ |
11 | |
12 | #include <linux/module.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/comedi/comedidev.h> |
15 | #include <linux/comedi/comedi_8255.h> |
16 | |
17 | #include "amplc_pc236.h" |
18 | |
19 | static void pc236_intr_update(struct comedi_device *dev, bool enable) |
20 | { |
21 | const struct pc236_board *board = dev->board_ptr; |
22 | struct pc236_private *devpriv = dev->private; |
23 | unsigned long flags; |
24 | |
25 | spin_lock_irqsave(&dev->spinlock, flags); |
26 | devpriv->enable_irq = enable; |
27 | if (board->intr_update_cb) |
28 | board->intr_update_cb(dev, enable); |
29 | spin_unlock_irqrestore(lock: &dev->spinlock, flags); |
30 | } |
31 | |
32 | /* |
33 | * This function is called when an interrupt occurs to check whether |
34 | * the interrupt has been marked as enabled and was generated by the |
35 | * board. If so, the function prepares the hardware for the next |
36 | * interrupt. |
37 | * Returns false if the interrupt should be ignored. |
38 | */ |
39 | static bool pc236_intr_check(struct comedi_device *dev) |
40 | { |
41 | const struct pc236_board *board = dev->board_ptr; |
42 | struct pc236_private *devpriv = dev->private; |
43 | bool retval = false; |
44 | unsigned long flags; |
45 | |
46 | spin_lock_irqsave(&dev->spinlock, flags); |
47 | if (devpriv->enable_irq) { |
48 | if (board->intr_chk_clr_cb) |
49 | retval = board->intr_chk_clr_cb(dev); |
50 | else |
51 | retval = true; |
52 | } |
53 | spin_unlock_irqrestore(lock: &dev->spinlock, flags); |
54 | |
55 | return retval; |
56 | } |
57 | |
58 | static int pc236_intr_insn(struct comedi_device *dev, |
59 | struct comedi_subdevice *s, struct comedi_insn *insn, |
60 | unsigned int *data) |
61 | { |
62 | data[1] = 0; |
63 | return insn->n; |
64 | } |
65 | |
66 | static int pc236_intr_cmdtest(struct comedi_device *dev, |
67 | struct comedi_subdevice *s, |
68 | struct comedi_cmd *cmd) |
69 | { |
70 | int err = 0; |
71 | |
72 | /* Step 1 : check if triggers are trivially valid */ |
73 | |
74 | err |= comedi_check_trigger_src(src: &cmd->start_src, TRIG_NOW); |
75 | err |= comedi_check_trigger_src(src: &cmd->scan_begin_src, TRIG_EXT); |
76 | err |= comedi_check_trigger_src(src: &cmd->convert_src, TRIG_FOLLOW); |
77 | err |= comedi_check_trigger_src(src: &cmd->scan_end_src, TRIG_COUNT); |
78 | err |= comedi_check_trigger_src(src: &cmd->stop_src, TRIG_NONE); |
79 | |
80 | if (err) |
81 | return 1; |
82 | |
83 | /* Step 2a : make sure trigger sources are unique */ |
84 | /* Step 2b : and mutually compatible */ |
85 | |
86 | /* Step 3: check it arguments are trivially valid */ |
87 | |
88 | err |= comedi_check_trigger_arg_is(arg: &cmd->start_arg, val: 0); |
89 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_begin_arg, val: 0); |
90 | err |= comedi_check_trigger_arg_is(arg: &cmd->convert_arg, val: 0); |
91 | err |= comedi_check_trigger_arg_is(arg: &cmd->scan_end_arg, |
92 | val: cmd->chanlist_len); |
93 | err |= comedi_check_trigger_arg_is(arg: &cmd->stop_arg, val: 0); |
94 | |
95 | if (err) |
96 | return 3; |
97 | |
98 | /* Step 4: fix up any arguments */ |
99 | |
100 | /* Step 5: check channel list if it exists */ |
101 | |
102 | return 0; |
103 | } |
104 | |
105 | static int pc236_intr_cmd(struct comedi_device *dev, struct comedi_subdevice *s) |
106 | { |
107 | pc236_intr_update(dev, enable: true); |
108 | |
109 | return 0; |
110 | } |
111 | |
112 | static int pc236_intr_cancel(struct comedi_device *dev, |
113 | struct comedi_subdevice *s) |
114 | { |
115 | pc236_intr_update(dev, enable: false); |
116 | |
117 | return 0; |
118 | } |
119 | |
120 | static irqreturn_t pc236_interrupt(int irq, void *d) |
121 | { |
122 | struct comedi_device *dev = d; |
123 | struct comedi_subdevice *s = dev->read_subdev; |
124 | bool handled; |
125 | |
126 | handled = pc236_intr_check(dev); |
127 | if (dev->attached && handled) { |
128 | unsigned short val = 0; |
129 | |
130 | comedi_buf_write_samples(s, data: &val, nsamples: 1); |
131 | comedi_handle_events(dev, s); |
132 | } |
133 | return IRQ_RETVAL(handled); |
134 | } |
135 | |
136 | int amplc_pc236_common_attach(struct comedi_device *dev, unsigned long iobase, |
137 | unsigned int irq, unsigned long req_irq_flags) |
138 | { |
139 | struct comedi_subdevice *s; |
140 | int ret; |
141 | |
142 | dev->iobase = iobase; |
143 | |
144 | ret = comedi_alloc_subdevices(dev, num_subdevices: 2); |
145 | if (ret) |
146 | return ret; |
147 | |
148 | s = &dev->subdevices[0]; |
149 | /* digital i/o subdevice (8255) */ |
150 | ret = subdev_8255_io_init(dev, s, regbase: 0x00); |
151 | if (ret) |
152 | return ret; |
153 | |
154 | s = &dev->subdevices[1]; |
155 | dev->read_subdev = s; |
156 | s->type = COMEDI_SUBD_UNUSED; |
157 | pc236_intr_update(dev, enable: false); |
158 | if (irq) { |
159 | if (request_irq(irq, handler: pc236_interrupt, flags: req_irq_flags, |
160 | name: dev->board_name, dev) >= 0) { |
161 | dev->irq = irq; |
162 | s->type = COMEDI_SUBD_DI; |
163 | s->subdev_flags = SDF_READABLE | SDF_CMD_READ; |
164 | s->n_chan = 1; |
165 | s->maxdata = 1; |
166 | s->range_table = &range_digital; |
167 | s->insn_bits = pc236_intr_insn; |
168 | s->len_chanlist = 1; |
169 | s->do_cmdtest = pc236_intr_cmdtest; |
170 | s->do_cmd = pc236_intr_cmd; |
171 | s->cancel = pc236_intr_cancel; |
172 | } |
173 | } |
174 | |
175 | return 0; |
176 | } |
177 | EXPORT_SYMBOL_GPL(amplc_pc236_common_attach); |
178 | |
179 | static int __init amplc_pc236_common_init(void) |
180 | { |
181 | return 0; |
182 | } |
183 | module_init(amplc_pc236_common_init); |
184 | |
185 | static void __exit amplc_pc236_common_exit(void) |
186 | { |
187 | } |
188 | module_exit(amplc_pc236_common_exit); |
189 | |
190 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
191 | MODULE_DESCRIPTION("Comedi helper for amplc_pc236 and amplc_pci236" ); |
192 | MODULE_LICENSE("GPL" ); |
193 | |