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 | ||
ce157f80 | 25 | #include <linux/module.h> |
33782dd5 | 26 | #include <linux/pci.h> |
abac8b54 HS |
27 | #include <linux/interrupt.h> |
28 | #include <linux/sched.h> | |
29 | ||
3d41c443 HS |
30 | #include "../comedidev.h" |
31 | #include "comedi_fc.h" | |
bf36f012 | 32 | #include "amcc_s5933.h" |
3d41c443 | 33 | |
1bc5062a HS |
34 | /* |
35 | * PCI bar 1 register I/O map | |
36 | */ | |
37 | #define APCI3501_AO_CTRL_STATUS_REG 0x00 | |
38 | #define APCI3501_AO_CTRL_BIPOLAR (1 << 0) | |
39 | #define APCI3501_AO_STATUS_READY (1 << 8) | |
40 | #define APCI3501_AO_DATA_REG 0x04 | |
26273698 HS |
41 | #define APCI3501_AO_DATA_CHAN(x) ((x) << 0) |
42 | #define APCI3501_AO_DATA_VAL(x) ((x) << 8) | |
43 | #define APCI3501_AO_DATA_BIPOLAR (1 << 31) | |
1bc5062a | 44 | #define APCI3501_AO_TRIG_SCS_REG 0x08 |
9798df72 HS |
45 | #define APCI3501_TIMER_SYNC_REG 0x20 |
46 | #define APCI3501_TIMER_RELOAD_REG 0x24 | |
47 | #define APCI3501_TIMER_TIMEBASE_REG 0x28 | |
48 | #define APCI3501_TIMER_CTRL_REG 0x2c | |
49 | #define APCI3501_TIMER_STATUS_REG 0x30 | |
50 | #define APCI3501_TIMER_IRQ_REG 0x34 | |
51 | #define APCI3501_TIMER_WARN_RELOAD_REG 0x38 | |
52 | #define APCI3501_TIMER_WARN_TIMEBASE_REG 0x3c | |
1bc5062a HS |
53 | #define APCI3501_DO_REG 0x40 |
54 | #define APCI3501_DI_REG 0x50 | |
55 | ||
25b9b873 HS |
56 | /* |
57 | * AMCC S5933 NVRAM | |
58 | */ | |
59 | #define NVRAM_USER_DATA_START 0x100 | |
60 | ||
61 | #define NVCMD_BEGIN_READ (0x7 << 5) | |
62 | #define NVCMD_LOAD_LOW (0x4 << 5) | |
63 | #define NVCMD_LOAD_HIGH (0x5 << 5) | |
64 | ||
65 | /* | |
66 | * Function types stored in the eeprom | |
67 | */ | |
68 | #define EEPROM_DIGITALINPUT 0 | |
69 | #define EEPROM_DIGITALOUTPUT 1 | |
70 | #define EEPROM_ANALOGINPUT 2 | |
71 | #define EEPROM_ANALOGOUTPUT 3 | |
72 | #define EEPROM_TIMER 4 | |
73 | #define EEPROM_WATCHDOG 5 | |
74 | #define EEPROM_TIMER_WATCHDOG_COUNTER 10 | |
75 | ||
53b28a23 HS |
76 | struct apci3501_private { |
77 | int i_IobaseAmcc; | |
78 | struct task_struct *tsk_Current; | |
79 | unsigned char b_TimerSelectMode; | |
53b28a23 HS |
80 | }; |
81 | ||
99c9fa48 HS |
82 | static struct comedi_lrange apci3501_ao_range = { |
83 | 2, { | |
84 | BIP_RANGE(10), | |
85 | UNI_RANGE(10) | |
86 | } | |
87 | }; | |
88 | ||
5458f3e7 HS |
89 | static int apci3501_wait_for_dac(struct comedi_device *dev) |
90 | { | |
91 | unsigned int status; | |
92 | ||
93 | do { | |
94 | status = inl(dev->iobase + APCI3501_AO_CTRL_STATUS_REG); | |
95 | } while (!(status & APCI3501_AO_STATUS_READY)); | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
298ab7de HS |
100 | static int apci3501_ao_insn_write(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 val = 0; | |
108 | int i; | |
109 | int ret; | |
110 | ||
111 | /* | |
112 | * All analog output channels have the same output range. | |
113 | * 14-bit bipolar: 0-10V | |
114 | * 13-bit unipolar: +/-10V | |
115 | * Changing the range of one channel changes all of them! | |
116 | */ | |
117 | if (range) { | |
118 | outl(0, dev->iobase + APCI3501_AO_CTRL_STATUS_REG); | |
119 | } else { | |
120 | val |= APCI3501_AO_DATA_BIPOLAR; | |
121 | outl(APCI3501_AO_CTRL_BIPOLAR, | |
122 | dev->iobase + APCI3501_AO_CTRL_STATUS_REG); | |
123 | } | |
124 | ||
125 | val |= APCI3501_AO_DATA_CHAN(chan); | |
126 | ||
127 | for (i = 0; i < insn->n; i++) { | |
128 | if (range == 1) { | |
129 | if (data[i] > 0x1fff) { | |
130 | dev_err(dev->class_dev, | |
131 | "Unipolar resolution is only 13-bits\n"); | |
132 | return -EINVAL; | |
133 | } | |
134 | } | |
135 | ||
136 | ret = apci3501_wait_for_dac(dev); | |
137 | if (ret) | |
138 | return ret; | |
139 | ||
140 | outl(val | APCI3501_AO_DATA_VAL(data[i]), | |
141 | dev->iobase + APCI3501_AO_DATA_REG); | |
142 | } | |
143 | ||
144 | return insn->n; | |
145 | } | |
146 | ||
53b28a23 HS |
147 | #include "addi-data/hwdrv_apci3501.c" |
148 | ||
513192fc HS |
149 | static int apci3501_di_insn_bits(struct comedi_device *dev, |
150 | struct comedi_subdevice *s, | |
151 | struct comedi_insn *insn, | |
152 | unsigned int *data) | |
153 | { | |
1bc5062a | 154 | data[1] = inl(dev->iobase + APCI3501_DI_REG) & 0x3; |
513192fc HS |
155 | |
156 | return insn->n; | |
157 | } | |
158 | ||
953a36c4 HS |
159 | static int apci3501_do_insn_bits(struct comedi_device *dev, |
160 | struct comedi_subdevice *s, | |
161 | struct comedi_insn *insn, | |
162 | unsigned int *data) | |
163 | { | |
1bc5062a | 164 | s->state = inl(dev->iobase + APCI3501_DO_REG); |
953a36c4 | 165 | |
97f4289a | 166 | if (comedi_dio_update_state(s, data)) |
1bc5062a | 167 | outl(s->state, dev->iobase + APCI3501_DO_REG); |
953a36c4 HS |
168 | |
169 | data[1] = s->state; | |
170 | ||
171 | return insn->n; | |
172 | } | |
173 | ||
25b9b873 HS |
174 | static void apci3501_eeprom_wait(unsigned long iobase) |
175 | { | |
176 | unsigned char val; | |
177 | ||
178 | do { | |
179 | val = inb(iobase + AMCC_OP_REG_MCSR_NVCMD); | |
180 | } while (val & 0x80); | |
181 | } | |
182 | ||
183 | static unsigned short apci3501_eeprom_readw(unsigned long iobase, | |
184 | unsigned short addr) | |
185 | { | |
186 | unsigned short val = 0; | |
187 | unsigned char tmp; | |
188 | unsigned char i; | |
189 | ||
190 | /* Add the offset to the start of the user data */ | |
191 | addr += NVRAM_USER_DATA_START; | |
192 | ||
193 | for (i = 0; i < 2; i++) { | |
194 | /* Load the low 8 bit address */ | |
195 | outb(NVCMD_LOAD_LOW, iobase + AMCC_OP_REG_MCSR_NVCMD); | |
196 | apci3501_eeprom_wait(iobase); | |
197 | outb((addr + i) & 0xff, iobase + AMCC_OP_REG_MCSR_NVDATA); | |
198 | apci3501_eeprom_wait(iobase); | |
199 | ||
200 | /* Load the high 8 bit address */ | |
201 | outb(NVCMD_LOAD_HIGH, iobase + AMCC_OP_REG_MCSR_NVCMD); | |
202 | apci3501_eeprom_wait(iobase); | |
203 | outb(((addr + i) >> 8) & 0xff, | |
204 | iobase + AMCC_OP_REG_MCSR_NVDATA); | |
205 | apci3501_eeprom_wait(iobase); | |
206 | ||
207 | /* Read the eeprom data byte */ | |
208 | outb(NVCMD_BEGIN_READ, iobase + AMCC_OP_REG_MCSR_NVCMD); | |
209 | apci3501_eeprom_wait(iobase); | |
210 | tmp = inb(iobase + AMCC_OP_REG_MCSR_NVDATA); | |
211 | apci3501_eeprom_wait(iobase); | |
212 | ||
213 | if (i == 0) | |
214 | val |= tmp; | |
215 | else | |
216 | val |= (tmp << 8); | |
217 | } | |
218 | ||
219 | return val; | |
220 | } | |
221 | ||
222 | static int apci3501_eeprom_get_ao_n_chan(struct comedi_device *dev) | |
223 | { | |
015aebe7 | 224 | struct apci3501_private *devpriv = dev->private; |
25b9b873 HS |
225 | unsigned long iobase = devpriv->i_IobaseAmcc; |
226 | unsigned char nfuncs; | |
227 | int i; | |
228 | ||
229 | nfuncs = apci3501_eeprom_readw(iobase, 10) & 0xff; | |
230 | ||
231 | /* Read functionality details */ | |
232 | for (i = 0; i < nfuncs; i++) { | |
233 | unsigned short offset = i * 4; | |
234 | unsigned short addr; | |
235 | unsigned char func; | |
236 | unsigned short val; | |
237 | ||
238 | func = apci3501_eeprom_readw(iobase, 12 + offset) & 0x3f; | |
239 | addr = apci3501_eeprom_readw(iobase, 14 + offset); | |
240 | ||
241 | if (func == EEPROM_ANALOGOUTPUT) { | |
242 | val = apci3501_eeprom_readw(iobase, addr + 10); | |
243 | return (val >> 4) & 0x3ff; | |
244 | } | |
245 | } | |
246 | return 0; | |
247 | } | |
248 | ||
87c38fbe | 249 | static int apci3501_eeprom_insn_read(struct comedi_device *dev, |
8861b456 HS |
250 | struct comedi_subdevice *s, |
251 | struct comedi_insn *insn, | |
252 | unsigned int *data) | |
253 | { | |
015aebe7 | 254 | struct apci3501_private *devpriv = dev->private; |
25b9b873 | 255 | unsigned short addr = CR_CHAN(insn->chanspec); |
8861b456 | 256 | |
25b9b873 | 257 | data[0] = apci3501_eeprom_readw(devpriv->i_IobaseAmcc, 2 * addr); |
8861b456 HS |
258 | |
259 | return insn->n; | |
260 | } | |
261 | ||
b2b82184 | 262 | static irqreturn_t apci3501_interrupt(int irq, void *d) |
8861b456 HS |
263 | { |
264 | struct comedi_device *dev = d; | |
015aebe7 | 265 | struct apci3501_private *devpriv = dev->private; |
b2b82184 HS |
266 | unsigned int ui_Timer_AOWatchdog; |
267 | unsigned long ul_Command1; | |
268 | int i_temp; | |
269 | ||
270 | /* Disable Interrupt */ | |
9798df72 | 271 | ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG); |
b2b82184 | 272 | ul_Command1 = (ul_Command1 & 0xFFFFF9FDul); |
9798df72 | 273 | outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG); |
b2b82184 | 274 | |
9798df72 | 275 | ui_Timer_AOWatchdog = inl(dev->iobase + APCI3501_TIMER_IRQ_REG) & 0x1; |
b2b82184 HS |
276 | if ((!ui_Timer_AOWatchdog)) { |
277 | comedi_error(dev, "IRQ from unknown source"); | |
278 | return IRQ_NONE; | |
279 | } | |
8861b456 | 280 | |
b2b82184 HS |
281 | /* Enable Interrupt Send a signal to from kernel to user space */ |
282 | send_sig(SIGIO, devpriv->tsk_Current, 0); | |
9798df72 | 283 | ul_Command1 = inl(dev->iobase + APCI3501_TIMER_CTRL_REG); |
b2b82184 | 284 | ul_Command1 = ((ul_Command1 & 0xFFFFF9FDul) | 1 << 1); |
9798df72 HS |
285 | outl(ul_Command1, dev->iobase + APCI3501_TIMER_CTRL_REG); |
286 | i_temp = inl(dev->iobase + APCI3501_TIMER_STATUS_REG) & 0x1; | |
b2b82184 HS |
287 | |
288 | return IRQ_HANDLED; | |
8861b456 HS |
289 | } |
290 | ||
678a4d3a | 291 | static int apci3501_reset(struct comedi_device *dev) |
8861b456 | 292 | { |
26273698 HS |
293 | unsigned int val; |
294 | int chan; | |
5458f3e7 | 295 | int ret; |
678a4d3a | 296 | |
26273698 | 297 | /* Reset all digital outputs to "0" */ |
1bc5062a | 298 | outl(0x0, dev->iobase + APCI3501_DO_REG); |
678a4d3a | 299 | |
26273698 HS |
300 | /* Default all analog outputs to 0V (bipolar) */ |
301 | outl(APCI3501_AO_CTRL_BIPOLAR, | |
302 | dev->iobase + APCI3501_AO_CTRL_STATUS_REG); | |
303 | val = APCI3501_AO_DATA_BIPOLAR | APCI3501_AO_DATA_VAL(0); | |
678a4d3a | 304 | |
26273698 HS |
305 | /* Set all analog output channels */ |
306 | for (chan = 0; chan < 8; chan++) { | |
5458f3e7 HS |
307 | ret = apci3501_wait_for_dac(dev); |
308 | if (ret) { | |
309 | dev_warn(dev->class_dev, | |
310 | "%s: DAC not-ready for channel %i\n", | |
26273698 | 311 | __func__, chan); |
5458f3e7 | 312 | } else { |
26273698 HS |
313 | outl(val | APCI3501_AO_DATA_CHAN(chan), |
314 | dev->iobase + APCI3501_AO_DATA_REG); | |
678a4d3a HS |
315 | } |
316 | } | |
8861b456 | 317 | |
8861b456 HS |
318 | return 0; |
319 | } | |
320 | ||
8861b456 HS |
321 | static int apci3501_auto_attach(struct comedi_device *dev, |
322 | unsigned long context_unused) | |
323 | { | |
324 | struct pci_dev *pcidev = comedi_to_pci_dev(dev); | |
015aebe7 | 325 | struct apci3501_private *devpriv; |
8861b456 | 326 | struct comedi_subdevice *s; |
25b9b873 | 327 | int ao_n_chan; |
694dcf50 | 328 | int ret; |
8861b456 | 329 | |
0bdab509 | 330 | devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
8861b456 HS |
331 | if (!devpriv) |
332 | return -ENOMEM; | |
8861b456 | 333 | |
818f569f | 334 | ret = comedi_pci_enable(dev); |
8861b456 HS |
335 | if (ret) |
336 | return ret; | |
337 | ||
1bdc8402 | 338 | dev->iobase = pci_resource_start(pcidev, 1); |
1bdc8402 | 339 | devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0); |
8861b456 | 340 | |
25b9b873 | 341 | ao_n_chan = apci3501_eeprom_get_ao_n_chan(dev); |
8861b456 HS |
342 | |
343 | if (pcidev->irq > 0) { | |
b2b82184 | 344 | ret = request_irq(pcidev->irq, apci3501_interrupt, IRQF_SHARED, |
8861b456 HS |
345 | dev->board_name, dev); |
346 | if (ret == 0) | |
347 | dev->irq = pcidev->irq; | |
348 | } | |
349 | ||
694dcf50 | 350 | ret = comedi_alloc_subdevices(dev, 5); |
8861b456 HS |
351 | if (ret) |
352 | return ret; | |
353 | ||
694dcf50 | 354 | /* Initialize the analog output subdevice */ |
8861b456 | 355 | s = &dev->subdevices[0]; |
25b9b873 HS |
356 | if (ao_n_chan) { |
357 | s->type = COMEDI_SUBD_AO; | |
358 | s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; | |
359 | s->n_chan = ao_n_chan; | |
360 | s->maxdata = 0x3fff; | |
99c9fa48 | 361 | s->range_table = &apci3501_ao_range; |
298ab7de | 362 | s->insn_write = apci3501_ao_insn_write; |
8861b456 | 363 | } else { |
25b9b873 | 364 | s->type = COMEDI_SUBD_UNUSED; |
8861b456 | 365 | } |
25b9b873 | 366 | |
694dcf50 HS |
367 | /* Initialize the digital input subdevice */ |
368 | s = &dev->subdevices[1]; | |
513192fc HS |
369 | s->type = COMEDI_SUBD_DI; |
370 | s->subdev_flags = SDF_READABLE; | |
371 | s->n_chan = 2; | |
372 | s->maxdata = 1; | |
373 | s->range_table = &range_digital; | |
374 | s->insn_bits = apci3501_di_insn_bits; | |
8861b456 | 375 | |
953a36c4 | 376 | /* Initialize the digital output subdevice */ |
694dcf50 | 377 | s = &dev->subdevices[2]; |
953a36c4 HS |
378 | s->type = COMEDI_SUBD_DO; |
379 | s->subdev_flags = SDF_WRITEABLE; | |
380 | s->n_chan = 2; | |
381 | s->maxdata = 1; | |
382 | s->range_table = &range_digital; | |
383 | s->insn_bits = apci3501_do_insn_bits; | |
8861b456 | 384 | |
694dcf50 HS |
385 | /* Initialize the timer/watchdog subdevice */ |
386 | s = &dev->subdevices[3]; | |
3d596e50 HS |
387 | s->type = COMEDI_SUBD_TIMER; |
388 | s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON; | |
389 | s->n_chan = 1; | |
390 | s->maxdata = 0; | |
391 | s->len_chanlist = 1; | |
392 | s->range_table = &range_digital; | |
393 | s->insn_write = i_APCI3501_StartStopWriteTimerCounterWatchdog; | |
394 | s->insn_read = i_APCI3501_ReadTimerCounterWatchdog; | |
395 | s->insn_config = i_APCI3501_ConfigTimerCounterWatchdog; | |
8861b456 | 396 | |
694dcf50 HS |
397 | /* Initialize the eeprom subdevice */ |
398 | s = &dev->subdevices[4]; | |
87c38fbe HS |
399 | s->type = COMEDI_SUBD_MEMORY; |
400 | s->subdev_flags = SDF_READABLE | SDF_INTERNAL; | |
401 | s->n_chan = 256; | |
402 | s->maxdata = 0xffff; | |
403 | s->insn_read = apci3501_eeprom_insn_read; | |
8861b456 | 404 | |
678a4d3a | 405 | apci3501_reset(dev); |
8861b456 HS |
406 | return 0; |
407 | } | |
408 | ||
409 | static void apci3501_detach(struct comedi_device *dev) | |
410 | { | |
015aebe7 HS |
411 | if (dev->iobase) |
412 | apci3501_reset(dev); | |
413 | if (dev->irq) | |
414 | free_irq(dev->irq, dev); | |
7f072f54 | 415 | comedi_pci_disable(dev); |
8861b456 | 416 | } |
317285d7 | 417 | |
20a22b70 HS |
418 | static struct comedi_driver apci3501_driver = { |
419 | .driver_name = "addi_apci_3501", | |
420 | .module = THIS_MODULE, | |
8861b456 HS |
421 | .auto_attach = apci3501_auto_attach, |
422 | .detach = apci3501_detach, | |
20a22b70 HS |
423 | }; |
424 | ||
a690b7e5 | 425 | static int apci3501_pci_probe(struct pci_dev *dev, |
b8f4ac23 | 426 | const struct pci_device_id *id) |
20a22b70 | 427 | { |
b8f4ac23 | 428 | return comedi_pci_auto_config(dev, &apci3501_driver, id->driver_data); |
20a22b70 HS |
429 | } |
430 | ||
8861b456 HS |
431 | static DEFINE_PCI_DEVICE_TABLE(apci3501_pci_table) = { |
432 | { PCI_DEVICE(PCI_VENDOR_ID_ADDIDATA, 0x3001) }, | |
433 | { 0 } | |
434 | }; | |
435 | MODULE_DEVICE_TABLE(pci, apci3501_pci_table); | |
436 | ||
20a22b70 HS |
437 | static struct pci_driver apci3501_pci_driver = { |
438 | .name = "addi_apci_3501", | |
439 | .id_table = apci3501_pci_table, | |
440 | .probe = apci3501_pci_probe, | |
9901a4d7 | 441 | .remove = comedi_pci_auto_unconfig, |
20a22b70 HS |
442 | }; |
443 | module_comedi_pci_driver(apci3501_driver, apci3501_pci_driver); | |
90f703d3 | 444 | |
6ff35881 | 445 | MODULE_DESCRIPTION("ADDI-DATA APCI-3501 Analog output board"); |
90f703d3 | 446 | MODULE_AUTHOR("Comedi http://www.comedi.org"); |
90f703d3 | 447 | MODULE_LICENSE("GPL"); |