1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45#include <linux/module.h>
46#include <linux/delay.h>
47#include <linux/interrupt.h>
48
49#include "../comedi_pci.h"
50
51#define ICP_MULTI_ADC_CSR 0
52#define ICP_MULTI_AI 2
53#define ICP_MULTI_DAC_CSR 4
54#define ICP_MULTI_AO 6
55#define ICP_MULTI_DI 8
56#define ICP_MULTI_DO 0x0A
57#define ICP_MULTI_INT_EN 0x0C
58#define ICP_MULTI_INT_STAT 0x0E
59#define ICP_MULTI_CNTR0 0x10
60#define ICP_MULTI_CNTR1 0x12
61#define ICP_MULTI_CNTR2 0x14
62#define ICP_MULTI_CNTR3 0x16
63
64
65#define ADC_ST 0x0001
66#define ADC_BSY 0x0001
67#define ADC_BI 0x0010
68#define ADC_RA 0x0020
69#define ADC_DI 0x0040
70
71
72#define DAC_ST 0x0001
73#define DAC_BSY 0x0001
74#define DAC_BI 0x0010
75#define DAC_RA 0x0020
76
77
78#define ADC_READY 0x0001
79#define DAC_READY 0x0002
80#define DOUT_ERROR 0x0004
81#define DIN_STATUS 0x0008
82#define CIE0 0x0010
83#define CIE1 0x0020
84#define CIE2 0x0040
85#define CIE3 0x0080
86
87
88#define Status_IRQ 0x00ff
89
90
91static const struct comedi_lrange range_analog = {
92 4, {
93 UNI_RANGE(5),
94 UNI_RANGE(10),
95 BIP_RANGE(5),
96 BIP_RANGE(10)
97 }
98};
99
100static const char range_codes_analog[] = { 0x00, 0x20, 0x10, 0x30 };
101
102
103
104
105
106
107
108struct icp_multi_private {
109 unsigned int AdcCmdStatus;
110 unsigned int DacCmdStatus;
111 unsigned int IntEnable;
112 unsigned int IntStatus;
113 unsigned int act_chanlist[32];
114 unsigned char act_chanlist_len;
115 unsigned char act_chanlist_pos;
116 unsigned int *ai_chanlist;
117 unsigned int do_data;
118};
119
120static void setup_channel_list(struct comedi_device *dev,
121 struct comedi_subdevice *s,
122 unsigned int *chanlist, unsigned int n_chan)
123{
124 struct icp_multi_private *devpriv = dev->private;
125 unsigned int i, range, chanprog;
126 unsigned int diff;
127
128 devpriv->act_chanlist_len = n_chan;
129 devpriv->act_chanlist_pos = 0;
130
131 for (i = 0; i < n_chan; i++) {
132
133 chanprog = CR_CHAN(chanlist[i]);
134
135
136 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
137 diff = 1;
138 chanprog &= 0x0007;
139 } else {
140 diff = 0;
141 chanprog &= 0x000f;
142 }
143
144
145
146 devpriv->AdcCmdStatus &= 0xf00f;
147
148
149 if (diff) {
150
151 devpriv->AdcCmdStatus |= (chanprog << 9);
152 devpriv->AdcCmdStatus |= ADC_DI;
153 } else
154
155 devpriv->AdcCmdStatus |= (chanprog << 8);
156
157
158 range = range_codes_analog[CR_RANGE(chanlist[i])];
159
160 devpriv->AdcCmdStatus |= range;
161
162
163 writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
164 }
165}
166
167static int icp_multi_ai_eoc(struct comedi_device *dev,
168 struct comedi_subdevice *s,
169 struct comedi_insn *insn,
170 unsigned long context)
171{
172 unsigned int status;
173
174 status = readw(dev->mmio + ICP_MULTI_ADC_CSR);
175 if ((status & ADC_BSY) == 0)
176 return 0;
177 return -EBUSY;
178}
179
180static int icp_multi_insn_read_ai(struct comedi_device *dev,
181 struct comedi_subdevice *s,
182 struct comedi_insn *insn,
183 unsigned int *data)
184{
185 struct icp_multi_private *devpriv = dev->private;
186 int ret = 0;
187 int n;
188
189
190 devpriv->IntEnable &= ~ADC_READY;
191 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
192
193
194 devpriv->IntStatus |= ADC_READY;
195 writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
196
197
198 setup_channel_list(dev, s, &insn->chanspec, 1);
199
200 for (n = 0; n < insn->n; n++) {
201
202 devpriv->AdcCmdStatus |= ADC_ST;
203 writew(devpriv->AdcCmdStatus, dev->mmio + ICP_MULTI_ADC_CSR);
204 devpriv->AdcCmdStatus &= ~ADC_ST;
205
206 udelay(1);
207
208
209 ret = comedi_timeout(dev, s, insn, icp_multi_ai_eoc, 0);
210 if (ret)
211 break;
212
213 data[n] = (readw(dev->mmio + ICP_MULTI_AI) >> 4) & 0x0fff;
214 }
215
216
217 devpriv->IntEnable &= ~ADC_READY;
218 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
219
220
221 devpriv->IntStatus |= ADC_READY;
222 writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
223
224 return ret ? ret : n;
225}
226
227static int icp_multi_ao_eoc(struct comedi_device *dev,
228 struct comedi_subdevice *s,
229 struct comedi_insn *insn,
230 unsigned long context)
231{
232 unsigned int status;
233
234 status = readw(dev->mmio + ICP_MULTI_DAC_CSR);
235 if ((status & DAC_BSY) == 0)
236 return 0;
237 return -EBUSY;
238}
239
240static int icp_multi_ao_insn_write(struct comedi_device *dev,
241 struct comedi_subdevice *s,
242 struct comedi_insn *insn,
243 unsigned int *data)
244{
245 struct icp_multi_private *devpriv = dev->private;
246 unsigned int chan = CR_CHAN(insn->chanspec);
247 unsigned int range = CR_RANGE(insn->chanspec);
248 int i;
249
250
251 devpriv->IntEnable &= ~DAC_READY;
252 writew(devpriv->IntEnable, dev->mmio + ICP_MULTI_INT_EN);
253
254
255 devpriv->IntStatus |= DAC_READY;
256 writew(devpriv->IntStatus, dev->mmio + ICP_MULTI_INT_STAT);
257
258
259
260
261
262
263 devpriv->DacCmdStatus &= 0xfccf;
264 devpriv->DacCmdStatus |= range_codes_analog[range];
265 devpriv->DacCmdStatus |= (chan << 8);
266
267 writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
268
269 for (i = 0; i < insn->n; i++) {
270 unsigned int val = data[i];
271 int ret;
272
273
274
275 ret = comedi_timeout(dev, s, insn, icp_multi_ao_eoc, 0);
276 if (ret) {
277
278 devpriv->IntEnable &= ~DAC_READY;
279 writew(devpriv->IntEnable,
280 dev->mmio + ICP_MULTI_INT_EN);
281
282
283 devpriv->IntStatus |= DAC_READY;
284 writew(devpriv->IntStatus,
285 dev->mmio + ICP_MULTI_INT_STAT);
286
287 return ret;
288 }
289
290 writew(val, dev->mmio + ICP_MULTI_AO);
291
292
293 devpriv->DacCmdStatus |= DAC_ST;
294 writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
295 devpriv->DacCmdStatus &= ~DAC_ST;
296
297 s->readback[chan] = val;
298 }
299
300 return insn->n;
301}
302
303static int icp_multi_insn_bits_di(struct comedi_device *dev,
304 struct comedi_subdevice *s,
305 struct comedi_insn *insn,
306 unsigned int *data)
307{
308 data[1] = readw(dev->mmio + ICP_MULTI_DI);
309
310 return insn->n;
311}
312
313static int icp_multi_insn_bits_do(struct comedi_device *dev,
314 struct comedi_subdevice *s,
315 struct comedi_insn *insn,
316 unsigned int *data)
317{
318 if (comedi_dio_update_state(s, data))
319 writew(s->state, dev->mmio + ICP_MULTI_DO);
320
321 data[1] = s->state;
322
323 return insn->n;
324}
325
326static int icp_multi_insn_read_ctr(struct comedi_device *dev,
327 struct comedi_subdevice *s,
328 struct comedi_insn *insn, unsigned int *data)
329{
330 return 0;
331}
332
333static int icp_multi_insn_write_ctr(struct comedi_device *dev,
334 struct comedi_subdevice *s,
335 struct comedi_insn *insn,
336 unsigned int *data)
337{
338 return 0;
339}
340
341static irqreturn_t interrupt_service_icp_multi(int irq, void *d)
342{
343 struct comedi_device *dev = d;
344 int int_no;
345
346
347 int_no = readw(dev->mmio + ICP_MULTI_INT_STAT) & Status_IRQ;
348 if (!int_no)
349
350 return IRQ_NONE;
351
352
353 switch (int_no) {
354 case ADC_READY:
355 break;
356 case DAC_READY:
357 break;
358 case DOUT_ERROR:
359 break;
360 case DIN_STATUS:
361 break;
362 case CIE0:
363 break;
364 case CIE1:
365 break;
366 case CIE2:
367 break;
368 case CIE3:
369 break;
370 default:
371 break;
372 }
373
374 return IRQ_HANDLED;
375}
376
377#if 0
378static int check_channel_list(struct comedi_device *dev,
379 struct comedi_subdevice *s,
380 unsigned int *chanlist, unsigned int n_chan)
381{
382 unsigned int i;
383
384
385 if (n_chan < 1) {
386 dev_err(dev->class_dev, "range/channel list is empty!\n");
387 return 0;
388 }
389
390 for (i = 0; i < n_chan; i++) {
391
392 if (CR_AREF(chanlist[i]) == AREF_DIFF) {
393 if (CR_CHAN(chanlist[i]) > (s->nchan / 2)) {
394 dev_err(dev->class_dev,
395 "Incorrect differential ai ch-nr\n");
396 return 0;
397 }
398 } else {
399 if (CR_CHAN(chanlist[i]) > s->n_chan) {
400 dev_err(dev->class_dev,
401 "Incorrect ai channel number\n");
402 return 0;
403 }
404 }
405 }
406 return 1;
407}
408#endif
409
410static int icp_multi_reset(struct comedi_device *dev)
411{
412 struct icp_multi_private *devpriv = dev->private;
413 unsigned int i;
414
415
416 writew(0, dev->mmio + ICP_MULTI_INT_EN);
417 writew(0x00ff, dev->mmio + ICP_MULTI_INT_STAT);
418
419
420 for (i = 0; i < 4; i++) {
421 devpriv->DacCmdStatus &= 0xfcce;
422
423
424 devpriv->DacCmdStatus |= (i << 8);
425
426
427 writew(0, dev->mmio + ICP_MULTI_AO);
428
429
430 devpriv->DacCmdStatus |= DAC_ST;
431
432
433 writew(devpriv->DacCmdStatus, dev->mmio + ICP_MULTI_DAC_CSR);
434
435
436 udelay(1);
437 }
438
439
440 writew(0, dev->mmio + ICP_MULTI_DO);
441
442 return 0;
443}
444
445static int icp_multi_auto_attach(struct comedi_device *dev,
446 unsigned long context_unused)
447{
448 struct pci_dev *pcidev = comedi_to_pci_dev(dev);
449 struct icp_multi_private *devpriv;
450 struct comedi_subdevice *s;
451 int ret;
452
453 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
454 if (!devpriv)
455 return -ENOMEM;
456
457 ret = comedi_pci_enable(dev);
458 if (ret)
459 return ret;
460
461 dev->mmio = pci_ioremap_bar(pcidev, 2);
462 if (!dev->mmio)
463 return -ENOMEM;
464
465 ret = comedi_alloc_subdevices(dev, 5);
466 if (ret)
467 return ret;
468
469 icp_multi_reset(dev);
470
471 if (pcidev->irq) {
472 ret = request_irq(pcidev->irq, interrupt_service_icp_multi,
473 IRQF_SHARED, dev->board_name, dev);
474 if (ret == 0)
475 dev->irq = pcidev->irq;
476 }
477
478 s = &dev->subdevices[0];
479 dev->read_subdev = s;
480 s->type = COMEDI_SUBD_AI;
481 s->subdev_flags = SDF_READABLE | SDF_COMMON | SDF_GROUND | SDF_DIFF;
482 s->n_chan = 16;
483 s->maxdata = 0x0fff;
484 s->len_chanlist = 16;
485 s->range_table = &range_analog;
486 s->insn_read = icp_multi_insn_read_ai;
487
488 s = &dev->subdevices[1];
489 s->type = COMEDI_SUBD_AO;
490 s->subdev_flags = SDF_WRITABLE | SDF_GROUND | SDF_COMMON;
491 s->n_chan = 4;
492 s->maxdata = 0x0fff;
493 s->len_chanlist = 4;
494 s->range_table = &range_analog;
495 s->insn_write = icp_multi_ao_insn_write;
496
497 ret = comedi_alloc_subdev_readback(s);
498 if (ret)
499 return ret;
500
501 s = &dev->subdevices[2];
502 s->type = COMEDI_SUBD_DI;
503 s->subdev_flags = SDF_READABLE;
504 s->n_chan = 16;
505 s->maxdata = 1;
506 s->len_chanlist = 16;
507 s->range_table = &range_digital;
508 s->insn_bits = icp_multi_insn_bits_di;
509
510 s = &dev->subdevices[3];
511 s->type = COMEDI_SUBD_DO;
512 s->subdev_flags = SDF_WRITABLE;
513 s->n_chan = 8;
514 s->maxdata = 1;
515 s->len_chanlist = 8;
516 s->range_table = &range_digital;
517 s->insn_bits = icp_multi_insn_bits_do;
518
519 s = &dev->subdevices[4];
520 s->type = COMEDI_SUBD_COUNTER;
521 s->subdev_flags = SDF_WRITABLE;
522 s->n_chan = 4;
523 s->maxdata = 0xffff;
524 s->len_chanlist = 4;
525 s->state = 0;
526 s->insn_read = icp_multi_insn_read_ctr;
527 s->insn_write = icp_multi_insn_write_ctr;
528
529 return 0;
530}
531
532static void icp_multi_detach(struct comedi_device *dev)
533{
534 if (dev->mmio)
535 icp_multi_reset(dev);
536 comedi_pci_detach(dev);
537}
538
539static struct comedi_driver icp_multi_driver = {
540 .driver_name = "icp_multi",
541 .module = THIS_MODULE,
542 .auto_attach = icp_multi_auto_attach,
543 .detach = icp_multi_detach,
544};
545
546static int icp_multi_pci_probe(struct pci_dev *dev,
547 const struct pci_device_id *id)
548{
549 return comedi_pci_auto_config(dev, &icp_multi_driver, id->driver_data);
550}
551
552static const struct pci_device_id icp_multi_pci_table[] = {
553 { PCI_DEVICE(PCI_VENDOR_ID_ICP, 0x8000) },
554 { 0 }
555};
556MODULE_DEVICE_TABLE(pci, icp_multi_pci_table);
557
558static struct pci_driver icp_multi_pci_driver = {
559 .name = "icp_multi",
560 .id_table = icp_multi_pci_table,
561 .probe = icp_multi_pci_probe,
562 .remove = comedi_pci_auto_unconfig,
563};
564module_comedi_pci_driver(icp_multi_driver, icp_multi_pci_driver);
565
566MODULE_AUTHOR("Comedi http://www.comedi.org");
567MODULE_DESCRIPTION("Comedi low-level driver");
568MODULE_LICENSE("GPL");
569