3 Sensoray s526 Comedi driver
5 COMEDI - Linux Control and Measurement Device Interface
6 Copyright (C) 2000 David A. Schleef <ds@schleef.org>
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
20 Description: Sensoray 526 driver
21 Devices: [Sensoray] 526 (s526)
23 Everett Wang <everett.wang@everteq.com>
24 Updated: Thu, 14 Sep. 2006
31 Commands are not supported yet.
33 Configuration Options:
35 comedi_config /dev/comedi0 s526 0x2C0,0x3
39 #include <linux/module.h>
40 #include "../comedidev.h"
41 #include <asm/byteorder.h>
43 #define S526_START_AI_CONV 0
44 #define S526_AI_READ 0
47 #define S526_NUM_PORTS 27
78 struct counter_mode_register_t {
79 #if defined(__LITTLE_ENDIAN_BITFIELD)
80 unsigned short coutSource:1;
81 unsigned short coutPolarity:1;
82 unsigned short autoLoadResetRcap:3;
83 unsigned short hwCtEnableSource:2;
84 unsigned short ctEnableCtrl:2;
85 unsigned short clockSource:2;
86 unsigned short countDir:1;
87 unsigned short countDirCtrl:1;
88 unsigned short outputRegLatchCtrl:1;
89 unsigned short preloadRegSel:1;
90 unsigned short reserved:1;
91 #elif defined(__BIG_ENDIAN_BITFIELD)
92 unsigned short reserved:1;
93 unsigned short preloadRegSel:1;
94 unsigned short outputRegLatchCtrl:1;
95 unsigned short countDirCtrl:1;
96 unsigned short countDir:1;
97 unsigned short clockSource:2;
98 unsigned short ctEnableCtrl:2;
99 unsigned short hwCtEnableSource:2;
100 unsigned short autoLoadResetRcap:3;
101 unsigned short coutPolarity:1;
102 unsigned short coutSource:1;
104 #error Unknown bit field order
109 struct counter_mode_register_t reg;
110 unsigned short value;
113 struct s526_private {
114 unsigned int ao_readback[2];
115 unsigned int gpct_config[4];
116 unsigned short ai_config;
119 static int s526_gpct_rinsn(struct comedi_device *dev,
120 struct comedi_subdevice *s,
121 struct comedi_insn *insn,
124 unsigned int chan = CR_CHAN(insn->chanspec);
125 unsigned long chan_iobase = dev->iobase + chan * 8;
130 for (i = 0; i < insn->n; i++) {
131 /* Read the low word first */
132 lo = inw(chan_iobase + REG_C0L) & 0xffff;
133 hi = inw(chan_iobase + REG_C0H) & 0xff;
135 data[i] = (hi << 16) | lo;
141 static int s526_gpct_insn_config(struct comedi_device *dev,
142 struct comedi_subdevice *s,
143 struct comedi_insn *insn,
146 struct s526_private *devpriv = dev->private;
147 unsigned int chan = CR_CHAN(insn->chanspec);
148 unsigned long chan_iobase = dev->iobase + chan * 8;
152 /* Check what type of Counter the user requested, data[0] contains */
153 /* the Application type */
155 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
157 data[0]: Application Type
158 data[1]: Counter Mode Register Value
159 data[2]: Pre-load Register Value
160 data[3]: Conter Control Register
162 devpriv->gpct_config[chan] = data[0];
165 /* Example of Counter Application */
166 /* One-shot (software trigger) */
167 cmReg.reg.coutSource = 0; /* out RCAP */
168 cmReg.reg.coutPolarity = 1; /* Polarity inverted */
169 cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */
170 cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */
171 cmReg.reg.ctEnableCtrl = 2; /* Hardware */
172 cmReg.reg.clockSource = 2; /* Internal */
173 cmReg.reg.countDir = 1; /* Down */
174 cmReg.reg.countDirCtrl = 1; /* Software */
175 cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */
176 cmReg.reg.preloadRegSel = 0; /* PR0 */
177 cmReg.reg.reserved = 0;
179 outw(cmReg.value, chan_iobase + REG_C0M);
181 outw(0x0001, chan_iobase + REG_C0H);
182 outw(0x3C68, chan_iobase + REG_C0L);
184 /* Reset the counter */
185 outw(0x8000, chan_iobase + REG_C0C);
186 /* Load the counter from PR0 */
187 outw(0x4000, chan_iobase + REG_C0C);
189 /* Reset RCAP (fires one-shot) */
190 outw(0x0008, chan_iobase + REG_C0C);
195 /* Set Counter Mode Register */
196 cmReg.value = data[1] & 0xffff;
197 outw(cmReg.value, chan_iobase + REG_C0M);
199 /* Reset the counter if it is software preload */
200 if (cmReg.reg.autoLoadResetRcap == 0) {
201 /* Reset the counter */
202 outw(0x8000, chan_iobase + REG_C0C);
203 /* Load the counter from PR0
204 * outw(0x4000, chan_iobase + REG_C0C);
208 /* 0 quadrature, 1 software control */
209 cmReg.reg.countDirCtrl = 0;
211 /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
212 if (data[1] == GPCT_X2)
213 cmReg.reg.clockSource = 1;
214 else if (data[1] == GPCT_X4)
215 cmReg.reg.clockSource = 2;
217 cmReg.reg.clockSource = 0;
219 /* When to take into account the indexpulse: */
220 /*if (data[2] == GPCT_IndexPhaseLowLow) {
221 } else if (data[2] == GPCT_IndexPhaseLowHigh) {
222 } else if (data[2] == GPCT_IndexPhaseHighLow) {
223 } else if (data[2] == GPCT_IndexPhaseHighHigh) {
225 /* Take into account the index pulse? */
226 if (data[3] == GPCT_RESET_COUNTER_ON_INDEX)
227 /* Auto load with INDEX^ */
228 cmReg.reg.autoLoadResetRcap = 4;
230 /* Set Counter Mode Register */
231 cmReg.value = data[1] & 0xffff;
232 outw(cmReg.value, chan_iobase + REG_C0M);
234 /* Load the pre-load register high word */
235 val = (data[2] >> 16) & 0xffff;
236 outw(val, chan_iobase + REG_C0H);
238 /* Load the pre-load register low word */
239 val = data[2] & 0xffff;
240 outw(val, chan_iobase + REG_C0L);
242 /* Write the Counter Control Register */
244 val = data[3] & 0xffff;
245 outw(val, chan_iobase + REG_C0C);
247 /* Reset the counter if it is software preload */
248 if (cmReg.reg.autoLoadResetRcap == 0) {
249 /* Reset the counter */
250 outw(0x8000, chan_iobase + REG_C0C);
251 /* Load the counter from PR0 */
252 outw(0x4000, chan_iobase + REG_C0C);
257 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
259 data[0]: Application Type
260 data[1]: Counter Mode Register Value
261 data[2]: Pre-load Register 0 Value
262 data[3]: Pre-load Register 1 Value
263 data[4]: Conter Control Register
265 devpriv->gpct_config[chan] = data[0];
267 /* Set Counter Mode Register */
268 cmReg.value = data[1] & 0xffff;
269 cmReg.reg.preloadRegSel = 0; /* PR0 */
270 outw(cmReg.value, chan_iobase + REG_C0M);
272 /* Load the pre-load register 0 high word */
273 val = (data[2] >> 16) & 0xffff;
274 outw(val, chan_iobase + REG_C0H);
276 /* Load the pre-load register 0 low word */
277 val = data[2] & 0xffff;
278 outw(val, chan_iobase + REG_C0L);
280 /* Set Counter Mode Register */
281 cmReg.value = data[1] & 0xffff;
282 cmReg.reg.preloadRegSel = 1; /* PR1 */
283 outw(cmReg.value, chan_iobase + REG_C0M);
285 /* Load the pre-load register 1 high word */
286 val = (data[3] >> 16) & 0xffff;
287 outw(val, chan_iobase + REG_C0H);
289 /* Load the pre-load register 1 low word */
290 val = data[3] & 0xffff;
291 outw(val, chan_iobase + REG_C0L);
293 /* Write the Counter Control Register */
295 val = data[4] & 0xffff;
296 outw(val, chan_iobase + REG_C0C);
300 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
302 data[0]: Application Type
303 data[1]: Counter Mode Register Value
304 data[2]: Pre-load Register 0 Value
305 data[3]: Pre-load Register 1 Value
306 data[4]: Conter Control Register
308 devpriv->gpct_config[chan] = data[0];
310 /* Set Counter Mode Register */
311 cmReg.value = data[1] & 0xffff;
312 cmReg.reg.preloadRegSel = 0; /* PR0 */
313 outw(cmReg.value, chan_iobase + REG_C0M);
315 /* Load the pre-load register 0 high word */
316 val = (data[2] >> 16) & 0xffff;
317 outw(val, chan_iobase + REG_C0H);
319 /* Load the pre-load register 0 low word */
320 val = data[2] & 0xffff;
321 outw(val, chan_iobase + REG_C0L);
323 /* Set Counter Mode Register */
324 cmReg.value = data[1] & 0xffff;
325 cmReg.reg.preloadRegSel = 1; /* PR1 */
326 outw(cmReg.value, chan_iobase + REG_C0M);
328 /* Load the pre-load register 1 high word */
329 val = (data[3] >> 16) & 0xffff;
330 outw(val, chan_iobase + REG_C0H);
332 /* Load the pre-load register 1 low word */
333 val = data[3] & 0xffff;
334 outw(val, chan_iobase + REG_C0L);
336 /* Write the Counter Control Register */
338 val = data[4] & 0xffff;
339 outw(val, chan_iobase + REG_C0C);
350 static int s526_gpct_winsn(struct comedi_device *dev,
351 struct comedi_subdevice *s,
352 struct comedi_insn *insn,
355 struct s526_private *devpriv = dev->private;
356 unsigned int chan = CR_CHAN(insn->chanspec);
357 unsigned long chan_iobase = dev->iobase + chan * 8;
359 inw(chan_iobase + REG_C0M); /* Is this read required? */
361 /* Check what Application of Counter this channel is configured for */
362 switch (devpriv->gpct_config[chan]) {
363 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
364 /* data[0] contains the PULSE_WIDTH
365 data[1] contains the PULSE_PERIOD
366 @pre PULSE_PERIOD > PULSE_WIDTH > 0
367 The above periods must be expressed as a multiple of the
368 pulse frequency on the selected source
370 if ((data[1] <= data[0]) || !data[0])
373 /* Fall thru to write the PULSE_WIDTH */
375 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
376 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
377 outw((data[0] >> 16) & 0xffff, chan_iobase + REG_C0H);
378 outw(data[0] & 0xffff, chan_iobase + REG_C0L);
388 #define ISR_ADC_DONE 0x4
389 static int s526_ai_insn_config(struct comedi_device *dev,
390 struct comedi_subdevice *s,
391 struct comedi_insn *insn, unsigned int *data)
393 struct s526_private *devpriv = dev->private;
394 int result = -EINVAL;
401 /* data[0] : channels was set in relevant bits.
404 /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to
405 * enable channels here. The channel should be enabled in the
406 * INSN_READ handler. */
408 /* Enable ADC interrupt */
409 outw(ISR_ADC_DONE, dev->iobase + REG_IER);
410 devpriv->ai_config = (data[0] & 0x3ff) << 5;
412 devpriv->ai_config |= 0x8000; /* set the delay */
414 devpriv->ai_config |= 0x0001; /* ADC start bit */
419 static int s526_ai_eoc(struct comedi_device *dev,
420 struct comedi_subdevice *s,
421 struct comedi_insn *insn,
422 unsigned long context)
426 status = inw(dev->iobase + REG_ISR);
427 if (status & ISR_ADC_DONE)
432 static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
433 struct comedi_insn *insn, unsigned int *data)
435 struct s526_private *devpriv = dev->private;
436 unsigned int chan = CR_CHAN(insn->chanspec);
438 unsigned short value;
442 /* Set configured delay, enable channel for this channel only,
443 * select "ADC read" channel, set "ADC start" bit. */
444 value = (devpriv->ai_config & 0x8000) |
445 ((1 << 5) << chan) | (chan << 1) | 0x0001;
447 /* convert n samples */
448 for (n = 0; n < insn->n; n++) {
449 /* trigger conversion */
450 outw(value, dev->iobase + REG_ADC);
452 /* wait for conversion to end */
453 ret = comedi_timeout(dev, s, insn, s526_ai_eoc, 0);
457 outw(ISR_ADC_DONE, dev->iobase + REG_ISR);
460 d = inw(dev->iobase + REG_ADD);
463 data[n] = d ^ 0x8000;
466 /* return the number of samples read/written */
470 static int s526_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
471 struct comedi_insn *insn, unsigned int *data)
473 struct s526_private *devpriv = dev->private;
474 unsigned int chan = CR_CHAN(insn->chanspec);
479 outw(val, dev->iobase + REG_DAC);
481 for (i = 0; i < insn->n; i++) {
482 outw(data[i], dev->iobase + REG_ADD);
483 devpriv->ao_readback[chan] = data[i];
484 /* starts the D/A conversion */
485 outw(val + 1, dev->iobase + REG_DAC);
491 static int s526_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
492 struct comedi_insn *insn, unsigned int *data)
494 struct s526_private *devpriv = dev->private;
495 unsigned int chan = CR_CHAN(insn->chanspec);
498 for (i = 0; i < insn->n; i++)
499 data[i] = devpriv->ao_readback[chan];
504 static int s526_dio_insn_bits(struct comedi_device *dev,
505 struct comedi_subdevice *s,
506 struct comedi_insn *insn,
509 if (comedi_dio_update_state(s, data))
510 outw(s->state, dev->iobase + REG_DIO);
512 data[1] = inw(dev->iobase + REG_DIO) & 0xff;
517 static int s526_dio_insn_config(struct comedi_device *dev,
518 struct comedi_subdevice *s,
519 struct comedi_insn *insn,
522 unsigned int chan = CR_CHAN(insn->chanspec);
531 ret = comedi_dio_insn_config(dev, s, insn, data, mask);
535 /* bit 10/11 set the group 1/2's mode */
536 if (s->io_bits & 0x0f)
537 s->state |= (1 << 10);
539 s->state &= ~(1 << 10);
540 if (s->io_bits & 0xf0)
541 s->state |= (1 << 11);
543 s->state &= ~(1 << 11);
545 outw(s->state, dev->iobase + REG_DIO);
550 static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
552 struct s526_private *devpriv;
553 struct comedi_subdevice *s;
556 ret = comedi_request_region(dev, it->options[0], 0x40);
560 devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
564 ret = comedi_alloc_subdevices(dev, 4);
568 s = &dev->subdevices[0];
569 /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
570 s->type = COMEDI_SUBD_COUNTER;
571 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
573 s->maxdata = 0x00ffffff; /* 24 bit counter */
574 s->insn_read = s526_gpct_rinsn;
575 s->insn_config = s526_gpct_insn_config;
576 s->insn_write = s526_gpct_winsn;
578 s = &dev->subdevices[1];
579 /* analog input subdevice */
580 s->type = COMEDI_SUBD_AI;
581 s->subdev_flags = SDF_READABLE | SDF_DIFF;
582 /* channels 0 to 7 are the regular differential inputs */
583 /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */
586 s->range_table = &range_bipolar10;
587 s->len_chanlist = 16;
588 s->insn_read = s526_ai_rinsn;
589 s->insn_config = s526_ai_insn_config;
591 s = &dev->subdevices[2];
592 /* analog output subdevice */
593 s->type = COMEDI_SUBD_AO;
594 s->subdev_flags = SDF_WRITABLE;
597 s->range_table = &range_bipolar10;
598 s->insn_write = s526_ao_winsn;
599 s->insn_read = s526_ao_rinsn;
601 s = &dev->subdevices[3];
602 /* digital i/o subdevice */
603 s->type = COMEDI_SUBD_DIO;
604 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
607 s->range_table = &range_digital;
608 s->insn_bits = s526_dio_insn_bits;
609 s->insn_config = s526_dio_insn_config;
614 static struct comedi_driver s526_driver = {
615 .driver_name = "s526",
616 .module = THIS_MODULE,
617 .attach = s526_attach,
618 .detach = comedi_legacy_detach,
620 module_comedi_driver(s526_driver);
622 MODULE_AUTHOR("Comedi http://www.comedi.org");
623 MODULE_DESCRIPTION("Comedi low-level driver");
624 MODULE_LICENSE("GPL");