1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * addi_apci_3501.c |
4 | * Copyright (C) 2004,2005 ADDI-DATA GmbH for the source code of this module. |
5 | * Project manager: Eric Stolz |
6 | * |
7 | * ADDI-DATA GmbH |
8 | * Dieselstrasse 3 |
9 | * D-77833 Ottersweier |
10 | * Tel: +19(0)7223/9493-0 |
11 | * Fax: +49(0)7223/9493-92 |
12 | * http://www.addi-data.com |
13 | * info@addi-data.com |
14 | */ |
15 | |
16 | /* |
17 | * Driver: addi_apci_3501 |
18 | * Description: ADDI-DATA APCI-3501 Analog output board |
19 | * Devices: [ADDI-DATA] APCI-3501 (addi_apci_3501) |
20 | * Author: H Hartley Sweeten <hsweeten@visionengravers.com> |
21 | * Updated: Mon, 20 Jun 2016 10:57:01 -0700 |
22 | * Status: untested |
23 | * |
24 | * Configuration Options: not applicable, uses comedi PCI auto config |
25 | * |
26 | * This board has the following features: |
27 | * - 4 or 8 analog output channels |
28 | * - 2 optically isolated digital inputs |
29 | * - 2 optically isolated digital outputs |
30 | * - 1 12-bit watchdog/timer |
31 | * |
32 | * There are 2 versions of the APCI-3501: |
33 | * - APCI-3501-4 4 analog output channels |
34 | * - APCI-3501-8 8 analog output channels |
35 | * |
36 | * These boards use the same PCI Vendor/Device IDs. The number of output |
37 | * channels used by this driver is determined by reading the EEPROM on |
38 | * the board. |
39 | * |
40 | * The watchdog/timer subdevice is not currently supported. |
41 | */ |
42 | |
43 | #include <linux/module.h> |
44 | #include <linux/comedi/comedi_pci.h> |
45 | |
46 | #include "amcc_s5933.h" |
47 | |
48 | /* |
49 | * PCI bar 1 register I/O map |
50 | */ |
51 | #define APCI3501_AO_CTRL_STATUS_REG 0x00 |
52 | #define APCI3501_AO_CTRL_BIPOLAR BIT(0) |
53 | #define APCI3501_AO_STATUS_READY BIT(8) |
54 | #define APCI3501_AO_DATA_REG 0x04 |
55 | #define APCI3501_AO_DATA_CHAN(x) ((x) << 0) |
56 | #define APCI3501_AO_DATA_VAL(x) ((x) << 8) |
57 | #define APCI3501_AO_DATA_BIPOLAR BIT(31) |
58 | #define APCI3501_AO_TRIG_SCS_REG 0x08 |
59 | #define APCI3501_TIMER_BASE 0x20 |
60 | #define APCI3501_DO_REG 0x40 |
61 | #define APCI3501_DI_REG 0x50 |
62 | |
63 | /* |
64 | * AMCC S5933 NVRAM |
65 | */ |
66 | #define NVRAM_USER_DATA_START 0x100 |
67 | |
68 | #define NVCMD_BEGIN_READ (0x7 << 5) |
69 | #define NVCMD_LOAD_LOW (0x4 << 5) |
70 | #define NVCMD_LOAD_HIGH (0x5 << 5) |
71 | |
72 | /* |
73 | * Function types stored in the eeprom |
74 | */ |
75 | #define EEPROM_DIGITALINPUT 0 |
76 | #define EEPROM_DIGITALOUTPUT 1 |
77 | #define EEPROM_ANALOGINPUT 2 |
78 | #define EEPROM_ANALOGOUTPUT 3 |
79 | #define EEPROM_TIMER 4 |
80 | #define EEPROM_WATCHDOG 5 |
81 | #define EEPROM_TIMER_WATCHDOG_COUNTER 10 |
82 | |
83 | struct apci3501_private { |
84 | unsigned long amcc; |
85 | unsigned char timer_mode; |
86 | }; |
87 | |
88 | static const struct comedi_lrange apci3501_ao_range = { |
89 | 2, { |
90 | BIP_RANGE(10), |
91 | UNI_RANGE(10) |
92 | } |
93 | }; |
94 | |
95 | static int apci3501_wait_for_dac(struct comedi_device *dev) |
96 | { |
97 | unsigned int status; |
98 | |
99 | do { |
100 | status = inl(port: dev->iobase + APCI3501_AO_CTRL_STATUS_REG); |
101 | } while (!(status & APCI3501_AO_STATUS_READY)); |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static int apci3501_ao_insn_write(struct comedi_device *dev, |
107 | struct comedi_subdevice *s, |
108 | struct comedi_insn *insn, |
109 | unsigned int *data) |
110 | { |
111 | unsigned int chan = CR_CHAN(insn->chanspec); |
112 | unsigned int range = CR_RANGE(insn->chanspec); |
113 | unsigned int cfg = APCI3501_AO_DATA_CHAN(chan); |
114 | int ret; |
115 | int i; |
116 | |
117 | /* |
118 | * All analog output channels have the same output range. |
119 | * 14-bit bipolar: 0-10V |
120 | * 13-bit unipolar: +/-10V |
121 | * Changing the range of one channel changes all of them! |
122 | */ |
123 | if (range) { |
124 | outl(value: 0, port: dev->iobase + APCI3501_AO_CTRL_STATUS_REG); |
125 | } else { |
126 | cfg |= APCI3501_AO_DATA_BIPOLAR; |
127 | outl(APCI3501_AO_CTRL_BIPOLAR, |
128 | port: dev->iobase + APCI3501_AO_CTRL_STATUS_REG); |
129 | } |
130 | |
131 | for (i = 0; i < insn->n; i++) { |
132 | unsigned int val = data[i]; |
133 | |
134 | if (range == 1) { |
135 | if (data[i] > 0x1fff) { |
136 | dev_err(dev->class_dev, |
137 | "Unipolar resolution is only 13-bits\n" ); |
138 | return -EINVAL; |
139 | } |
140 | } |
141 | |
142 | ret = apci3501_wait_for_dac(dev); |
143 | if (ret) |
144 | return ret; |
145 | |
146 | outl(value: cfg | APCI3501_AO_DATA_VAL(val), |
147 | port: dev->iobase + APCI3501_AO_DATA_REG); |
148 | |
149 | s->readback[chan] = val; |
150 | } |
151 | |
152 | return insn->n; |
153 | } |
154 | |
155 | static int apci3501_di_insn_bits(struct comedi_device *dev, |
156 | struct comedi_subdevice *s, |
157 | struct comedi_insn *insn, |
158 | unsigned int *data) |
159 | { |
160 | data[1] = inl(port: dev->iobase + APCI3501_DI_REG) & 0x3; |
161 | |
162 | return insn->n; |
163 | } |
164 | |
165 | static int apci3501_do_insn_bits(struct comedi_device *dev, |
166 | struct comedi_subdevice *s, |
167 | struct comedi_insn *insn, |
168 | unsigned int *data) |
169 | { |
170 | s->state = inl(port: dev->iobase + APCI3501_DO_REG); |
171 | |
172 | if (comedi_dio_update_state(s, data)) |
173 | outl(value: s->state, port: dev->iobase + APCI3501_DO_REG); |
174 | |
175 | data[1] = s->state; |
176 | |
177 | return insn->n; |
178 | } |
179 | |
180 | static void apci3501_eeprom_wait(unsigned long iobase) |
181 | { |
182 | unsigned char val; |
183 | |
184 | do { |
185 | val = inb(port: iobase + AMCC_OP_REG_MCSR_NVCMD); |
186 | } while (val & 0x80); |
187 | } |
188 | |
189 | static unsigned short apci3501_eeprom_readw(unsigned long iobase, |
190 | unsigned short addr) |
191 | { |
192 | unsigned short val = 0; |
193 | unsigned char tmp; |
194 | unsigned char i; |
195 | |
196 | /* Add the offset to the start of the user data */ |
197 | addr += NVRAM_USER_DATA_START; |
198 | |
199 | for (i = 0; i < 2; i++) { |
200 | /* Load the low 8 bit address */ |
201 | outb(NVCMD_LOAD_LOW, port: iobase + AMCC_OP_REG_MCSR_NVCMD); |
202 | apci3501_eeprom_wait(iobase); |
203 | outb(value: (addr + i) & 0xff, port: iobase + AMCC_OP_REG_MCSR_NVDATA); |
204 | apci3501_eeprom_wait(iobase); |
205 | |
206 | /* Load the high 8 bit address */ |
207 | outb(NVCMD_LOAD_HIGH, port: iobase + AMCC_OP_REG_MCSR_NVCMD); |
208 | apci3501_eeprom_wait(iobase); |
209 | outb(value: ((addr + i) >> 8) & 0xff, |
210 | port: iobase + AMCC_OP_REG_MCSR_NVDATA); |
211 | apci3501_eeprom_wait(iobase); |
212 | |
213 | /* Read the eeprom data byte */ |
214 | outb(NVCMD_BEGIN_READ, port: iobase + AMCC_OP_REG_MCSR_NVCMD); |
215 | apci3501_eeprom_wait(iobase); |
216 | tmp = inb(port: iobase + AMCC_OP_REG_MCSR_NVDATA); |
217 | apci3501_eeprom_wait(iobase); |
218 | |
219 | if (i == 0) |
220 | val |= tmp; |
221 | else |
222 | val |= (tmp << 8); |
223 | } |
224 | |
225 | return val; |
226 | } |
227 | |
228 | static int apci3501_eeprom_get_ao_n_chan(struct comedi_device *dev) |
229 | { |
230 | struct apci3501_private *devpriv = dev->private; |
231 | unsigned char nfuncs; |
232 | int i; |
233 | |
234 | nfuncs = apci3501_eeprom_readw(iobase: devpriv->amcc, addr: 10) & 0xff; |
235 | |
236 | /* Read functionality details */ |
237 | for (i = 0; i < nfuncs; i++) { |
238 | unsigned short offset = i * 4; |
239 | unsigned short addr; |
240 | unsigned char func; |
241 | unsigned short val; |
242 | |
243 | func = apci3501_eeprom_readw(iobase: devpriv->amcc, addr: 12 + offset) & 0x3f; |
244 | addr = apci3501_eeprom_readw(iobase: devpriv->amcc, addr: 14 + offset); |
245 | |
246 | if (func == EEPROM_ANALOGOUTPUT) { |
247 | val = apci3501_eeprom_readw(iobase: devpriv->amcc, addr: addr + 10); |
248 | return (val >> 4) & 0x3ff; |
249 | } |
250 | } |
251 | return 0; |
252 | } |
253 | |
254 | static int apci3501_eeprom_insn_read(struct comedi_device *dev, |
255 | struct comedi_subdevice *s, |
256 | struct comedi_insn *insn, |
257 | unsigned int *data) |
258 | { |
259 | struct apci3501_private *devpriv = dev->private; |
260 | unsigned short addr = CR_CHAN(insn->chanspec); |
261 | unsigned int val; |
262 | unsigned int i; |
263 | |
264 | if (insn->n) { |
265 | /* No point reading the same EEPROM location more than once. */ |
266 | val = apci3501_eeprom_readw(iobase: devpriv->amcc, addr: 2 * addr); |
267 | for (i = 0; i < insn->n; i++) |
268 | data[i] = val; |
269 | } |
270 | |
271 | return insn->n; |
272 | } |
273 | |
274 | static int apci3501_reset(struct comedi_device *dev) |
275 | { |
276 | unsigned int val; |
277 | int chan; |
278 | int ret; |
279 | |
280 | /* Reset all digital outputs to "0" */ |
281 | outl(value: 0x0, port: dev->iobase + APCI3501_DO_REG); |
282 | |
283 | /* Default all analog outputs to 0V (bipolar) */ |
284 | outl(APCI3501_AO_CTRL_BIPOLAR, |
285 | port: dev->iobase + APCI3501_AO_CTRL_STATUS_REG); |
286 | val = APCI3501_AO_DATA_BIPOLAR | APCI3501_AO_DATA_VAL(0); |
287 | |
288 | /* Set all analog output channels */ |
289 | for (chan = 0; chan < 8; chan++) { |
290 | ret = apci3501_wait_for_dac(dev); |
291 | if (ret) { |
292 | dev_warn(dev->class_dev, |
293 | "%s: DAC not-ready for channel %i\n" , |
294 | __func__, chan); |
295 | } else { |
296 | outl(value: val | APCI3501_AO_DATA_CHAN(chan), |
297 | port: dev->iobase + APCI3501_AO_DATA_REG); |
298 | } |
299 | } |
300 | |
301 | return 0; |
302 | } |
303 | |
304 | static int apci3501_auto_attach(struct comedi_device *dev, |
305 | unsigned long context_unused) |
306 | { |
307 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
308 | struct apci3501_private *devpriv; |
309 | struct comedi_subdevice *s; |
310 | int ao_n_chan; |
311 | int ret; |
312 | |
313 | devpriv = comedi_alloc_devpriv(dev, size: sizeof(*devpriv)); |
314 | if (!devpriv) |
315 | return -ENOMEM; |
316 | |
317 | ret = comedi_pci_enable(dev); |
318 | if (ret) |
319 | return ret; |
320 | |
321 | devpriv->amcc = pci_resource_start(pcidev, 0); |
322 | dev->iobase = pci_resource_start(pcidev, 1); |
323 | |
324 | ao_n_chan = apci3501_eeprom_get_ao_n_chan(dev); |
325 | |
326 | ret = comedi_alloc_subdevices(dev, num_subdevices: 5); |
327 | if (ret) |
328 | return ret; |
329 | |
330 | /* Initialize the analog output subdevice */ |
331 | s = &dev->subdevices[0]; |
332 | if (ao_n_chan) { |
333 | s->type = COMEDI_SUBD_AO; |
334 | s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON; |
335 | s->n_chan = ao_n_chan; |
336 | s->maxdata = 0x3fff; |
337 | s->range_table = &apci3501_ao_range; |
338 | s->insn_write = apci3501_ao_insn_write; |
339 | |
340 | ret = comedi_alloc_subdev_readback(s); |
341 | if (ret) |
342 | return ret; |
343 | } else { |
344 | s->type = COMEDI_SUBD_UNUSED; |
345 | } |
346 | |
347 | /* Initialize the digital input subdevice */ |
348 | s = &dev->subdevices[1]; |
349 | s->type = COMEDI_SUBD_DI; |
350 | s->subdev_flags = SDF_READABLE; |
351 | s->n_chan = 2; |
352 | s->maxdata = 1; |
353 | s->range_table = &range_digital; |
354 | s->insn_bits = apci3501_di_insn_bits; |
355 | |
356 | /* Initialize the digital output subdevice */ |
357 | s = &dev->subdevices[2]; |
358 | s->type = COMEDI_SUBD_DO; |
359 | s->subdev_flags = SDF_WRITABLE; |
360 | s->n_chan = 2; |
361 | s->maxdata = 1; |
362 | s->range_table = &range_digital; |
363 | s->insn_bits = apci3501_do_insn_bits; |
364 | |
365 | /* Timer/Watchdog subdevice */ |
366 | s = &dev->subdevices[3]; |
367 | s->type = COMEDI_SUBD_UNUSED; |
368 | |
369 | /* Initialize the eeprom subdevice */ |
370 | s = &dev->subdevices[4]; |
371 | s->type = COMEDI_SUBD_MEMORY; |
372 | s->subdev_flags = SDF_READABLE | SDF_INTERNAL; |
373 | s->n_chan = 256; |
374 | s->maxdata = 0xffff; |
375 | s->insn_read = apci3501_eeprom_insn_read; |
376 | |
377 | apci3501_reset(dev); |
378 | return 0; |
379 | } |
380 | |
381 | static void apci3501_detach(struct comedi_device *dev) |
382 | { |
383 | if (dev->iobase) |
384 | apci3501_reset(dev); |
385 | comedi_pci_detach(dev); |
386 | } |
387 | |
388 | static struct comedi_driver apci3501_driver = { |
389 | .driver_name = "addi_apci_3501" , |
390 | .module = THIS_MODULE, |
391 | .auto_attach = apci3501_auto_attach, |
392 | .detach = apci3501_detach, |
393 | }; |
394 | |
395 | static int apci3501_pci_probe(struct pci_dev *dev, |
396 | const struct pci_device_id *id) |
397 | { |
398 | return comedi_pci_auto_config(pcidev: dev, driver: &apci3501_driver, context: id->driver_data); |
399 | } |
400 | |
401 | static const struct pci_device_id apci3501_pci_table[] = { |
402 | { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3001) }, |
403 | { 0 } |
404 | }; |
405 | MODULE_DEVICE_TABLE(pci, apci3501_pci_table); |
406 | |
407 | static struct pci_driver apci3501_pci_driver = { |
408 | .name = "addi_apci_3501" , |
409 | .id_table = apci3501_pci_table, |
410 | .probe = apci3501_pci_probe, |
411 | .remove = comedi_pci_auto_unconfig, |
412 | }; |
413 | module_comedi_pci_driver(apci3501_driver, apci3501_pci_driver); |
414 | |
415 | MODULE_DESCRIPTION("ADDI-DATA APCI-3501 Analog output board" ); |
416 | MODULE_AUTHOR("Comedi https://www.comedi.org" ); |
417 | MODULE_LICENSE("GPL" ); |
418 | |