1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * icp_multi.c |
4 | * Comedi driver for Inova ICP_MULTI board |
5 | * |
6 | * COMEDI - Linux Control and Measurement Device Interface |
7 | * Copyright (C) 1997-2002 David A. Schleef <ds@schleef.org> |
8 | */ |
9 | |
10 | /* |
11 | * Driver: icp_multi |
12 | * Description: Inova ICP_MULTI |
13 | * Devices: [Inova] ICP_MULTI (icp_multi) |
14 | * Author: Anne Smorthit <anne.smorthit@sfwte.ch> |
15 | * Status: works |
16 | * |
17 | * Configuration options: not applicable, uses PCI auto config |
18 | * |
19 | * The driver works for analog input and output and digital input and |
20 | * output. It does not work with interrupts or with the counters. Currently |
21 | * no support for DMA. |
22 | * |
23 | * It has 16 single-ended or 8 differential Analogue Input channels with |
24 | * 12-bit resolution. Ranges : 5V, 10V, +/-5V, +/-10V, 0..20mA and 4..20mA. |
25 | * Input ranges can be individually programmed for each channel. Voltage or |
26 | * current measurement is selected by jumper. |
27 | * |
28 | * There are 4 x 12-bit Analogue Outputs. Ranges : 5V, 10V, +/-5V, +/-10V |
29 | * |
30 | * 16 x Digital Inputs, 24V |
31 | * |
32 | * 8 x Digital Outputs, 24V, 1A |
33 | * |
34 | * 4 x 16-bit counters - not implemented |
35 | */ |
36 | |
37 | #include <linux/module.h> |
38 | #include <linux/delay.h> |
39 | #include <linux/comedi/comedi_pci.h> |
40 | |
41 | #define ICP_MULTI_ADC_CSR 0x00 /* R/W: ADC command/status register */ |
42 | #define ICP_MULTI_ADC_CSR_ST BIT(0) /* Start ADC */ |
43 | #define ICP_MULTI_ADC_CSR_BSY BIT(0) /* ADC busy */ |
44 | #define ICP_MULTI_ADC_CSR_BI BIT(4) /* Bipolar input range */ |
45 | #define ICP_MULTI_ADC_CSR_RA BIT(5) /* Input range 0 = 5V, 1 = 10V */ |
46 | #define ICP_MULTI_ADC_CSR_DI BIT(6) /* Input mode 1 = differential */ |
47 | #define ICP_MULTI_ADC_CSR_DI_CHAN(x) (((x) & 0x7) << 9) |
48 | #define ICP_MULTI_ADC_CSR_SE_CHAN(x) (((x) & 0xf) << 8) |
49 | #define ICP_MULTI_AI 2 /* R: Analogue input data */ |
50 | #define ICP_MULTI_DAC_CSR 0x04 /* R/W: DAC command/status register */ |
51 | #define ICP_MULTI_DAC_CSR_ST BIT(0) /* Start DAC */ |
52 | #define ICP_MULTI_DAC_CSR_BSY BIT(0) /* DAC busy */ |
53 | #define ICP_MULTI_DAC_CSR_BI BIT(4) /* Bipolar output range */ |
54 | #define ICP_MULTI_DAC_CSR_RA BIT(5) /* Output range 0 = 5V, 1 = 10V */ |
55 | #define ICP_MULTI_DAC_CSR_CHAN(x) (((x) & 0x3) << 8) |
56 | #define ICP_MULTI_AO 6 /* R/W: Analogue output data */ |
57 | #define ICP_MULTI_DI 8 /* R/W: Digital inputs */ |
58 | #define ICP_MULTI_DO 0x0A /* R/W: Digital outputs */ |
59 | #define ICP_MULTI_INT_EN 0x0c /* R/W: Interrupt enable register */ |
60 | #define ICP_MULTI_INT_STAT 0x0e /* R/W: Interrupt status register */ |
61 | #define ICP_MULTI_INT_ADC_RDY BIT(0) /* A/D conversion ready interrupt */ |
62 | #define ICP_MULTI_INT_DAC_RDY BIT(1) /* D/A conversion ready interrupt */ |
63 | #define ICP_MULTI_INT_DOUT_ERR BIT(2) /* Digital output error interrupt */ |
64 | #define ICP_MULTI_INT_DIN_STAT BIT(3) /* Digital input status change int. */ |
65 | #define ICP_MULTI_INT_CIE0 BIT(4) /* Counter 0 overrun interrupt */ |
66 | #define ICP_MULTI_INT_CIE1 BIT(5) /* Counter 1 overrun interrupt */ |
67 | #define ICP_MULTI_INT_CIE2 BIT(6) /* Counter 2 overrun interrupt */ |
68 | #define ICP_MULTI_INT_CIE3 BIT(7) /* Counter 3 overrun interrupt */ |
69 | #define ICP_MULTI_INT_MASK 0xff /* All interrupts */ |
70 | #define ICP_MULTI_CNTR0 0x10 /* R/W: Counter 0 */ |
71 | #define ICP_MULTI_CNTR1 0x12 /* R/W: counter 1 */ |
72 | #define ICP_MULTI_CNTR2 0x14 /* R/W: Counter 2 */ |
73 | #define ICP_MULTI_CNTR3 0x16 /* R/W: Counter 3 */ |
74 | |
75 | /* analog input and output have the same range options */ |
76 | static const struct comedi_lrange icp_multi_ranges = { |
77 | 4, { |
78 | UNI_RANGE(5), |
79 | UNI_RANGE(10), |
80 | BIP_RANGE(5), |
81 | BIP_RANGE(10) |
82 | } |
83 | }; |
84 | |
85 | static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 }; |
86 | |
87 | static int icp_multi_ai_eoc(struct comedi_device *dev, |
88 | struct comedi_subdevice *s, |
89 | struct comedi_insn *insn, |
90 | unsigned long context) |
91 | { |
92 | unsigned int status; |
93 | |
94 | status = readw(addr: dev->mmio + ICP_MULTI_ADC_CSR); |
95 | if ((status & ICP_MULTI_ADC_CSR_BSY) == 0) |
96 | return 0; |
97 | return -EBUSY; |
98 | } |
99 | |
100 | static int icp_multi_ai_insn_read(struct comedi_device *dev, |
101 | struct comedi_subdevice *s, |
102 | struct comedi_insn *insn, |
103 | unsigned int *data) |
104 | { |
105 | unsigned int chan = CR_CHAN(insn->chanspec); |
106 | unsigned int range = CR_RANGE(insn->chanspec); |
107 | unsigned int aref = CR_AREF(insn->chanspec); |
108 | unsigned int adc_csr; |
109 | int ret = 0; |
110 | int n; |
111 | |
112 | /* Set mode and range data for specified channel */ |
113 | if (aref == AREF_DIFF) { |
114 | adc_csr = ICP_MULTI_ADC_CSR_DI_CHAN(chan) | |
115 | ICP_MULTI_ADC_CSR_DI; |
116 | } else { |
117 | adc_csr = ICP_MULTI_ADC_CSR_SE_CHAN(chan); |
118 | } |
119 | adc_csr |= range_codes_analog[range]; |
120 | writew(val: adc_csr, addr: dev->mmio + ICP_MULTI_ADC_CSR); |
121 | |
122 | for (n = 0; n < insn->n; n++) { |
123 | /* Set start ADC bit */ |
124 | writew(val: adc_csr | ICP_MULTI_ADC_CSR_ST, |
125 | addr: dev->mmio + ICP_MULTI_ADC_CSR); |
126 | |
127 | udelay(1); |
128 | |
129 | /* Wait for conversion to complete, or get fed up waiting */ |
130 | ret = comedi_timeout(dev, s, insn, cb: icp_multi_ai_eoc, context: 0); |
131 | if (ret) |
132 | break; |
133 | |
134 | data[n] = (readw(addr: dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff; |
135 | } |
136 | |
137 | return ret ? ret : n; |
138 | } |
139 | |
140 | static int icp_multi_ao_ready(struct comedi_device *dev, |
141 | struct comedi_subdevice *s, |
142 | struct comedi_insn *insn, |
143 | unsigned long context) |
144 | { |
145 | unsigned int status; |
146 | |
147 | status = readw(addr: dev->mmio + ICP_MULTI_DAC_CSR); |
148 | if ((status & ICP_MULTI_DAC_CSR_BSY) == 0) |
149 | return 0; |
150 | return -EBUSY; |
151 | } |
152 | |
153 | static int icp_multi_ao_insn_write(struct comedi_device *dev, |
154 | struct comedi_subdevice *s, |
155 | struct comedi_insn *insn, |
156 | unsigned int *data) |
157 | { |
158 | unsigned int chan = CR_CHAN(insn->chanspec); |
159 | unsigned int range = CR_RANGE(insn->chanspec); |
160 | unsigned int dac_csr; |
161 | int i; |
162 | |
163 | /* Select channel and range */ |
164 | dac_csr = ICP_MULTI_DAC_CSR_CHAN(chan); |
165 | dac_csr |= range_codes_analog[range]; |
166 | writew(val: dac_csr, addr: dev->mmio + ICP_MULTI_DAC_CSR); |
167 | |
168 | for (i = 0; i < insn->n; i++) { |
169 | unsigned int val = data[i]; |
170 | int ret; |
171 | |
172 | /* Wait for analog output to be ready for new data */ |
173 | ret = comedi_timeout(dev, s, insn, cb: icp_multi_ao_ready, context: 0); |
174 | if (ret) |
175 | return ret; |
176 | |
177 | writew(val, addr: dev->mmio + ICP_MULTI_AO); |
178 | |
179 | /* Set start conversion bit to write data to channel */ |
180 | writew(val: dac_csr | ICP_MULTI_DAC_CSR_ST, |
181 | addr: dev->mmio + ICP_MULTI_DAC_CSR); |
182 | |
183 | s->readback[chan] = val; |
184 | } |
185 | |
186 | return insn->n; |
187 | } |
188 | |
189 | static int icp_multi_di_insn_bits(struct comedi_device *dev, |
190 | struct comedi_subdevice *s, |
191 | struct comedi_insn *insn, |
192 | unsigned int *data) |
193 | { |
194 | data[1] = readw(addr: dev->mmio + ICP_MULTI_DI); |
195 | |
196 | return insn->n; |
197 | } |
198 | |
199 | static int icp_multi_do_insn_bits(struct comedi_device *dev, |
200 | struct comedi_subdevice *s, |
201 | struct comedi_insn *insn, |
202 | unsigned int *data) |
203 | { |
204 | if (comedi_dio_update_state(s, data)) |
205 | writew(val: s->state, addr: dev->mmio + ICP_MULTI_DO); |
206 | |
207 | data[1] = s->state; |
208 | |
209 | return insn->n; |
210 | } |
211 | |
212 | static int icp_multi_reset(struct comedi_device *dev) |
213 | { |
214 | int i; |
215 | |
216 | /* Disable all interrupts and clear any requests */ |
217 | writew(val: 0, addr: dev->mmio + ICP_MULTI_INT_EN); |
218 | writew(ICP_MULTI_INT_MASK, addr: dev->mmio + ICP_MULTI_INT_STAT); |
219 | |
220 | /* Reset the analog output channels to 0V */ |
221 | for (i = 0; i < 4; i++) { |
222 | unsigned int dac_csr = ICP_MULTI_DAC_CSR_CHAN(i); |
223 | |
224 | /* Select channel and 0..5V range */ |
225 | writew(val: dac_csr, addr: dev->mmio + ICP_MULTI_DAC_CSR); |
226 | |
227 | /* Output 0V */ |
228 | writew(val: 0, addr: dev->mmio + ICP_MULTI_AO); |
229 | |
230 | /* Set start conversion bit to write data to channel */ |
231 | writew(val: dac_csr | ICP_MULTI_DAC_CSR_ST, |
232 | addr: dev->mmio + ICP_MULTI_DAC_CSR); |
233 | udelay(1); |
234 | } |
235 | |
236 | /* Digital outputs to 0 */ |
237 | writew(val: 0, addr: dev->mmio + ICP_MULTI_DO); |
238 | |
239 | return 0; |
240 | } |
241 | |
242 | static int icp_multi_auto_attach(struct comedi_device *dev, |
243 | unsigned long context_unused) |
244 | { |
245 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
246 | struct comedi_subdevice *s; |
247 | int ret; |
248 | |
249 | ret = comedi_pci_enable(dev); |
250 | if (ret) |
251 | return ret; |
252 | |
253 | dev->mmio = pci_ioremap_bar(pdev: pcidev, bar: 2); |
254 | if (!dev->mmio) |
255 | return -ENOMEM; |
256 | |
257 | ret = comedi_alloc_subdevices(dev, num_subdevices: 4); |
258 | if (ret) |
259 | return ret; |
260 | |
261 | icp_multi_reset(dev); |
262 | |
263 | /* Analog Input subdevice */ |
264 | s = &dev->subdevices[0]; |
265 | s->type = COMEDI_SUBD_AI; |
266 | s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF; |
267 | s->n_chan = 16; |
268 | s->maxdata = 0x0fff; |
269 | s->range_table = &icp_multi_ranges; |
270 | s->insn_read = icp_multi_ai_insn_read; |
271 | |
272 | /* Analog Output subdevice */ |
273 | s = &dev->subdevices[1]; |
274 | s->type = COMEDI_SUBD_AO; |
275 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; |
276 | s->n_chan = 4; |
277 | s->maxdata = 0x0fff; |
278 | s->range_table = &icp_multi_ranges; |
279 | s->insn_write = icp_multi_ao_insn_write; |
280 | |
281 | ret = comedi_alloc_subdev_readback(s); |
282 | if (ret) |
283 | return ret; |
284 | |
285 | /* Digital Input subdevice */ |
286 | s = &dev->subdevices[2]; |
287 | s->type = COMEDI_SUBD_DI; |
288 | s->subdev_flags = SDF_READABLE; |
289 | s->n_chan = 16; |
290 | s->maxdata = 1; |
291 | s->range_table = &range_digital; |
292 | s->insn_bits = icp_multi_di_insn_bits; |
293 | |
294 | /* Digital Output subdevice */ |
295 | s = &dev->subdevices[3]; |
296 | s->type = COMEDI_SUBD_DO; |
297 | s->subdev_flags = SDF_WRITABLE; |
298 | s->n_chan = 8; |
299 | s->maxdata = 1; |
300 | s->range_table = &range_digital; |
301 | s->insn_bits = icp_multi_do_insn_bits; |
302 | |
303 | return 0; |
304 | } |
305 | |
306 | static struct comedi_driver icp_multi_driver = { |
307 | .driver_name = "icp_multi" , |
308 | .module = THIS_MODULE, |
309 | .auto_attach = icp_multi_auto_attach, |
310 | .detach = comedi_pci_detach, |
311 | }; |
312 | |
313 | static int icp_multi_pci_probe(struct pci_dev *dev, |
314 | const struct pci_device_id *id) |
315 | { |
316 | return comedi_pci_auto_config(pcidev: dev, driver: &icp_multi_driver, context: id->driver_data); |
317 | } |
318 | |
319 | static const struct pci_device_id icp_multi_pci_table[] = { |
320 | { PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) }, |
321 | { 0 } |
322 | }; |
323 | MODULE_DEVICE_TABLE(pci, icp_multi_pci_table); |
324 | |
325 | static struct pci_driver icp_multi_pci_driver = { |
326 | .name = "icp_multi" , |
327 | .id_table = icp_multi_pci_table, |
328 | .probe = icp_multi_pci_probe, |
329 | .remove = comedi_pci_auto_unconfig, |
330 | }; |
331 | module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver); |
332 | |
333 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
334 | MODULE_DESCRIPTION("Comedi driver for Inova ICP_MULTI board" ); |
335 | MODULE_LICENSE("GPL" ); |
336 | |