1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * adq12b.c |
4 | * Driver for MicroAxial ADQ12-B data acquisition and control card |
5 | * written by jeremy theler <thelerg@ib.cnea.gov.ar> |
6 | * instituto balseiro |
7 | * commission nacional de energia atomica |
8 | * universidad nacional de cuyo |
9 | * argentina |
10 | * |
11 | * COMEDI - Linux Control and Measurement Device Interface |
12 | * Copyright (C) 2000 David A. Schleef <ds@schleef.org> |
13 | */ |
14 | |
15 | /* |
16 | * Driver: adq12b |
17 | * Description: Driver for MicroAxial ADQ12-B data acquisition and control card |
18 | * Devices: [MicroAxial] ADQ12-B (adq12b) |
19 | * Author: jeremy theler <thelerg@ib.cnea.gov.ar> |
20 | * Updated: Thu, 21 Feb 2008 02:56:27 -0300 |
21 | * Status: works |
22 | * |
23 | * Configuration options: |
24 | * [0] - I/O base address (set with hardware jumpers) |
25 | * address jumper JADR |
26 | * 0x300 1 (factory default) |
27 | * 0x320 2 |
28 | * 0x340 3 |
29 | * 0x360 4 |
30 | * 0x380 5 |
31 | * 0x3A0 6 |
32 | * [1] - Analog Input unipolar/bipolar selection |
33 | * selection option JUB |
34 | * bipolar 0 2-3 (factory default) |
35 | * unipolar 1 1-2 |
36 | * [2] - Analog Input single-ended/differential selection |
37 | * selection option JCHA JCHB |
38 | * single-ended 0 1-2 1-2 (factory default) |
39 | * differential 1 2-3 2-3 |
40 | * |
41 | * Driver for the acquisition card ADQ12-B (without any add-on). |
42 | * |
43 | * - Analog input is subdevice 0 (16 channels single-ended or 8 differential) |
44 | * - Digital input is subdevice 1 (5 channels) |
45 | * - Digital output is subdevice 1 (8 channels) |
46 | * - The PACER is not supported in this version |
47 | */ |
48 | |
49 | #include <linux/module.h> |
50 | #include <linux/delay.h> |
51 | #include <linux/comedi/comedidev.h> |
52 | |
53 | /* address scheme (page 2.17 of the manual) */ |
54 | #define ADQ12B_CTREG 0x00 |
55 | #define ADQ12B_CTREG_MSKP BIT(7) /* enable pacer interrupt */ |
56 | #define ADQ12B_CTREG_GTP BIT(6) /* enable pacer */ |
57 | #define ADQ12B_CTREG_RANGE(x) ((x) << 4) |
58 | #define ADQ12B_CTREG_CHAN(x) ((x) << 0) |
59 | #define ADQ12B_STINR 0x00 |
60 | #define ADQ12B_STINR_OUT2 BIT(7) /* timer 2 output state */ |
61 | #define ADQ12B_STINR_OUTP BIT(6) /* pacer output state */ |
62 | #define ADQ12B_STINR_EOC BIT(5) /* A/D end-of-conversion */ |
63 | #define ADQ12B_STINR_IN_MASK (0x1f << 0) |
64 | #define ADQ12B_OUTBR 0x04 |
65 | #define ADQ12B_ADLOW 0x08 |
66 | #define ADQ12B_ADHIG 0x09 |
67 | #define ADQ12B_TIMER_BASE 0x0c |
68 | |
69 | /* available ranges through the PGA gains */ |
70 | static const struct comedi_lrange range_adq12b_ai_bipolar = { |
71 | 4, { |
72 | BIP_RANGE(5), |
73 | BIP_RANGE(2), |
74 | BIP_RANGE(1), |
75 | BIP_RANGE(0.5) |
76 | } |
77 | }; |
78 | |
79 | static const struct comedi_lrange range_adq12b_ai_unipolar = { |
80 | 4, { |
81 | UNI_RANGE(5), |
82 | UNI_RANGE(2), |
83 | UNI_RANGE(1), |
84 | UNI_RANGE(0.5) |
85 | } |
86 | }; |
87 | |
88 | struct adq12b_private { |
89 | unsigned int last_ctreg; |
90 | }; |
91 | |
92 | static int adq12b_ai_eoc(struct comedi_device *dev, |
93 | struct comedi_subdevice *s, |
94 | struct comedi_insn *insn, |
95 | unsigned long context) |
96 | { |
97 | unsigned char status; |
98 | |
99 | status = inb(port: dev->iobase + ADQ12B_STINR); |
100 | if (status & ADQ12B_STINR_EOC) |
101 | return 0; |
102 | return -EBUSY; |
103 | } |
104 | |
105 | static int adq12b_ai_insn_read(struct comedi_device *dev, |
106 | struct comedi_subdevice *s, |
107 | struct comedi_insn *insn, |
108 | unsigned int *data) |
109 | { |
110 | struct adq12b_private *devpriv = dev->private; |
111 | unsigned int chan = CR_CHAN(insn->chanspec); |
112 | unsigned int range = CR_RANGE(insn->chanspec); |
113 | unsigned int val; |
114 | int ret; |
115 | int i; |
116 | |
117 | /* change channel and range only if it is different from the previous */ |
118 | val = ADQ12B_CTREG_RANGE(range) | ADQ12B_CTREG_CHAN(chan); |
119 | if (val != devpriv->last_ctreg) { |
120 | outb(value: val, port: dev->iobase + ADQ12B_CTREG); |
121 | devpriv->last_ctreg = val; |
122 | usleep_range(min: 50, max: 100); /* wait for the mux to settle */ |
123 | } |
124 | |
125 | val = inb(port: dev->iobase + ADQ12B_ADLOW); /* trigger A/D */ |
126 | |
127 | for (i = 0; i < insn->n; i++) { |
128 | ret = comedi_timeout(dev, s, insn, cb: adq12b_ai_eoc, context: 0); |
129 | if (ret) |
130 | return ret; |
131 | |
132 | val = inb(port: dev->iobase + ADQ12B_ADHIG) << 8; |
133 | val |= inb(port: dev->iobase + ADQ12B_ADLOW); /* retriggers A/D */ |
134 | |
135 | data[i] = val; |
136 | } |
137 | |
138 | return insn->n; |
139 | } |
140 | |
141 | static int adq12b_di_insn_bits(struct comedi_device *dev, |
142 | struct comedi_subdevice *s, |
143 | struct comedi_insn *insn, unsigned int *data) |
144 | { |
145 | /* only bits 0-4 have information about digital inputs */ |
146 | data[1] = (inb(port: dev->iobase + ADQ12B_STINR) & ADQ12B_STINR_IN_MASK); |
147 | |
148 | return insn->n; |
149 | } |
150 | |
151 | static int adq12b_do_insn_bits(struct comedi_device *dev, |
152 | struct comedi_subdevice *s, |
153 | struct comedi_insn *insn, |
154 | unsigned int *data) |
155 | { |
156 | unsigned int mask; |
157 | unsigned int chan; |
158 | unsigned int val; |
159 | |
160 | mask = comedi_dio_update_state(s, data); |
161 | if (mask) { |
162 | for (chan = 0; chan < 8; chan++) { |
163 | if ((mask >> chan) & 0x01) { |
164 | val = (s->state >> chan) & 0x01; |
165 | outb(value: (val << 3) | chan, |
166 | port: dev->iobase + ADQ12B_OUTBR); |
167 | } |
168 | } |
169 | } |
170 | |
171 | data[1] = s->state; |
172 | |
173 | return insn->n; |
174 | } |
175 | |
176 | static int adq12b_attach(struct comedi_device *dev, struct comedi_devconfig *it) |
177 | { |
178 | struct adq12b_private *devpriv; |
179 | struct comedi_subdevice *s; |
180 | int ret; |
181 | |
182 | ret = comedi_request_region(dev, start: it->options[0], len: 0x10); |
183 | if (ret) |
184 | return ret; |
185 | |
186 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
187 | if (!devpriv) |
188 | return -ENOMEM; |
189 | |
190 | devpriv->last_ctreg = -1; /* force ctreg update */ |
191 | |
192 | ret = comedi_alloc_subdevices(dev, num_subdevices: 3); |
193 | if (ret) |
194 | return ret; |
195 | |
196 | /* Analog Input subdevice */ |
197 | s = &dev->subdevices[0]; |
198 | s->type = COMEDI_SUBD_AI; |
199 | if (it->options[2]) { |
200 | s->subdev_flags = SDF_READABLE | SDF_DIFF; |
201 | s->n_chan = 8; |
202 | } else { |
203 | s->subdev_flags = SDF_READABLE | SDF_GROUND; |
204 | s->n_chan = 16; |
205 | } |
206 | s->maxdata = 0xfff; |
207 | s->range_table = it->options[1] ? &range_adq12b_ai_unipolar |
208 | : &range_adq12b_ai_bipolar; |
209 | s->insn_read = adq12b_ai_insn_read; |
210 | |
211 | /* Digital Input subdevice */ |
212 | s = &dev->subdevices[1]; |
213 | s->type = COMEDI_SUBD_DI; |
214 | s->subdev_flags = SDF_READABLE; |
215 | s->n_chan = 5; |
216 | s->maxdata = 1; |
217 | s->range_table = &range_digital; |
218 | s->insn_bits = adq12b_di_insn_bits; |
219 | |
220 | /* Digital Output subdevice */ |
221 | s = &dev->subdevices[2]; |
222 | s->type = COMEDI_SUBD_DO; |
223 | s->subdev_flags = SDF_WRITABLE; |
224 | s->n_chan = 8; |
225 | s->maxdata = 1; |
226 | s->range_table = &range_digital; |
227 | s->insn_bits = adq12b_do_insn_bits; |
228 | |
229 | return 0; |
230 | } |
231 | |
232 | static struct comedi_driver adq12b_driver = { |
233 | .driver_name = "adq12b" , |
234 | .module = THIS_MODULE, |
235 | .attach = adq12b_attach, |
236 | .detach = comedi_legacy_detach, |
237 | }; |
238 | module_comedi_driver(adq12b_driver); |
239 | |
240 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
241 | MODULE_DESCRIPTION("Comedi low-level driver" ); |
242 | MODULE_LICENSE("GPL" ); |
243 | |