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