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.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 Description: Sensoray 526 driver
26 Devices: [Sensoray] 526 (s526)
28 Everett Wang <everett.wang@everteq.com>
29 Updated: Thu, 14 Sep. 2006
36 Commands are not supported yet.
38 Configuration Options:
40 comedi_config /dev/comedi0 s526 0x2C0,0x3
44 #include "../comedidev.h"
45 #include <linux/ioport.h>
46 #include <asm/byteorder.h>
50 #define S526_START_AI_CONV 0
51 #define S526_AI_READ 0
54 #define S526_IOSIZE 0x40
55 #define S526_NUM_PORTS 27
86 struct counter_mode_register_t {
87 #if defined(__LITTLE_ENDIAN_BITFIELD)
88 unsigned short coutSource:1;
89 unsigned short coutPolarity:1;
90 unsigned short autoLoadResetRcap:3;
91 unsigned short hwCtEnableSource:2;
92 unsigned short ctEnableCtrl:2;
93 unsigned short clockSource:2;
94 unsigned short countDir:1;
95 unsigned short countDirCtrl:1;
96 unsigned short outputRegLatchCtrl:1;
97 unsigned short preloadRegSel:1;
98 unsigned short reserved:1;
99 #elif defined(__BIG_ENDIAN_BITFIELD)
100 unsigned short reserved:1;
101 unsigned short preloadRegSel:1;
102 unsigned short outputRegLatchCtrl:1;
103 unsigned short countDirCtrl:1;
104 unsigned short countDir:1;
105 unsigned short clockSource:2;
106 unsigned short ctEnableCtrl:2;
107 unsigned short hwCtEnableSource:2;
108 unsigned short autoLoadResetRcap:3;
109 unsigned short coutPolarity:1;
110 unsigned short coutSource:1;
112 #error Unknown bit field order
117 struct counter_mode_register_t reg;
118 unsigned short value;
121 struct s526_private {
122 unsigned int ao_readback[2];
123 unsigned int gpct_config[4];
124 unsigned short ai_config;
127 static int s526_gpct_rinsn(struct comedi_device *dev,
128 struct comedi_subdevice *s,
129 struct comedi_insn *insn,
132 unsigned int chan = CR_CHAN(insn->chanspec);
133 unsigned long chan_iobase = dev->iobase + chan * 8;
138 for (i = 0; i < insn->n; i++) {
139 /* Read the low word first */
140 lo = inw(chan_iobase + REG_C0L) & 0xffff;
141 hi = inw(chan_iobase + REG_C0H) & 0xff;
143 data[i] = (hi << 16) | lo;
149 static int s526_gpct_insn_config(struct comedi_device *dev,
150 struct comedi_subdevice *s,
151 struct comedi_insn *insn,
154 struct s526_private *devpriv = dev->private;
155 unsigned int chan = CR_CHAN(insn->chanspec);
156 unsigned long chan_iobase = dev->iobase + chan * 8;
160 /* Check what type of Counter the user requested, data[0] contains */
161 /* the Application type */
163 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
165 data[0]: Application Type
166 data[1]: Counter Mode Register Value
167 data[2]: Pre-load Register Value
168 data[3]: Conter Control Register
170 devpriv->gpct_config[chan] = data[0];
173 /* Example of Counter Application */
174 /* One-shot (software trigger) */
175 cmReg.reg.coutSource = 0; /* out RCAP */
176 cmReg.reg.coutPolarity = 1; /* Polarity inverted */
177 cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */
178 cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */
179 cmReg.reg.ctEnableCtrl = 2; /* Hardware */
180 cmReg.reg.clockSource = 2; /* Internal */
181 cmReg.reg.countDir = 1; /* Down */
182 cmReg.reg.countDirCtrl = 1; /* Software */
183 cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */
184 cmReg.reg.preloadRegSel = 0; /* PR0 */
185 cmReg.reg.reserved = 0;
187 outw(cmReg.value, chan_iobase + REG_C0M);
189 outw(0x0001, chan_iobase + REG_C0H);
190 outw(0x3C68, chan_iobase + REG_C0L);
192 /* Reset the counter */
193 outw(0x8000, chan_iobase + REG_C0C);
194 /* Load the counter from PR0 */
195 outw(0x4000, chan_iobase + REG_C0C);
197 /* Reset RCAP (fires one-shot) */
198 outw(0x0008, chan_iobase + REG_C0C);
203 /* Set Counter Mode Register */
204 cmReg.value = data[1] & 0xffff;
205 outw(cmReg.value, chan_iobase + REG_C0M);
207 /* Reset the counter if it is software preload */
208 if (cmReg.reg.autoLoadResetRcap == 0) {
209 /* Reset the counter */
210 outw(0x8000, chan_iobase + REG_C0C);
211 /* Load the counter from PR0
212 * outw(0x4000, chan_iobase + REG_C0C);
216 /* 0 quadrature, 1 software control */
217 cmReg.reg.countDirCtrl = 0;
219 /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
220 if (data[1] == GPCT_X2)
221 cmReg.reg.clockSource = 1;
222 else if (data[1] == GPCT_X4)
223 cmReg.reg.clockSource = 2;
225 cmReg.reg.clockSource = 0;
227 /* When to take into account the indexpulse: */
228 /*if (data[2] == GPCT_IndexPhaseLowLow) {
229 } else if (data[2] == GPCT_IndexPhaseLowHigh) {
230 } else if (data[2] == GPCT_IndexPhaseHighLow) {
231 } else if (data[2] == GPCT_IndexPhaseHighHigh) {
233 /* Take into account the index pulse? */
234 if (data[3] == GPCT_RESET_COUNTER_ON_INDEX)
235 /* Auto load with INDEX^ */
236 cmReg.reg.autoLoadResetRcap = 4;
238 /* Set Counter Mode Register */
239 cmReg.value = data[1] & 0xffff;
240 outw(cmReg.value, chan_iobase + REG_C0M);
242 /* Load the pre-load register high word */
243 val = (data[2] >> 16) & 0xffff;
244 outw(val, chan_iobase + REG_C0H);
246 /* Load the pre-load register low word */
247 val = data[2] & 0xffff;
248 outw(val, chan_iobase + REG_C0L);
250 /* Write the Counter Control Register */
252 val = data[3] & 0xffff;
253 outw(val, chan_iobase + REG_C0C);
255 /* Reset the counter if it is software preload */
256 if (cmReg.reg.autoLoadResetRcap == 0) {
257 /* Reset the counter */
258 outw(0x8000, chan_iobase + REG_C0C);
259 /* Load the counter from PR0 */
260 outw(0x4000, chan_iobase + REG_C0C);
265 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
267 data[0]: Application Type
268 data[1]: Counter Mode Register Value
269 data[2]: Pre-load Register 0 Value
270 data[3]: Pre-load Register 1 Value
271 data[4]: Conter Control Register
273 devpriv->gpct_config[chan] = data[0];
275 /* Set Counter Mode Register */
276 cmReg.value = data[1] & 0xffff;
277 cmReg.reg.preloadRegSel = 0; /* PR0 */
278 outw(cmReg.value, chan_iobase + REG_C0M);
280 /* Load the pre-load register 0 high word */
281 val = (data[2] >> 16) & 0xffff;
282 outw(val, chan_iobase + REG_C0H);
284 /* Load the pre-load register 0 low word */
285 val = data[2] & 0xffff;
286 outw(val, chan_iobase + REG_C0L);
288 /* Set Counter Mode Register */
289 cmReg.value = data[1] & 0xffff;
290 cmReg.reg.preloadRegSel = 1; /* PR1 */
291 outw(cmReg.value, chan_iobase + REG_C0M);
293 /* Load the pre-load register 1 high word */
294 val = (data[3] >> 16) & 0xffff;
295 outw(val, chan_iobase + REG_C0H);
297 /* Load the pre-load register 1 low word */
298 val = data[3] & 0xffff;
299 outw(val, chan_iobase + REG_C0L);
301 /* Write the Counter Control Register */
303 val = data[4] & 0xffff;
304 outw(val, chan_iobase + REG_C0C);
308 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
310 data[0]: Application Type
311 data[1]: Counter Mode Register Value
312 data[2]: Pre-load Register 0 Value
313 data[3]: Pre-load Register 1 Value
314 data[4]: Conter Control Register
316 devpriv->gpct_config[chan] = data[0];
318 /* Set Counter Mode Register */
319 cmReg.value = data[1] & 0xffff;
320 cmReg.reg.preloadRegSel = 0; /* PR0 */
321 outw(cmReg.value, chan_iobase + REG_C0M);
323 /* Load the pre-load register 0 high word */
324 val = (data[2] >> 16) & 0xffff;
325 outw(val, chan_iobase + REG_C0H);
327 /* Load the pre-load register 0 low word */
328 val = data[2] & 0xffff;
329 outw(val, chan_iobase + REG_C0L);
331 /* Set Counter Mode Register */
332 cmReg.value = data[1] & 0xffff;
333 cmReg.reg.preloadRegSel = 1; /* PR1 */
334 outw(cmReg.value, chan_iobase + REG_C0M);
336 /* Load the pre-load register 1 high word */
337 val = (data[3] >> 16) & 0xffff;
338 outw(val, chan_iobase + REG_C0H);
340 /* Load the pre-load register 1 low word */
341 val = data[3] & 0xffff;
342 outw(val, chan_iobase + REG_C0L);
344 /* Write the Counter Control Register */
346 val = data[4] & 0xffff;
347 outw(val, chan_iobase + REG_C0C);
359 static int s526_gpct_winsn(struct comedi_device *dev,
360 struct comedi_subdevice *s,
361 struct comedi_insn *insn,
364 struct s526_private *devpriv = dev->private;
365 unsigned int chan = CR_CHAN(insn->chanspec);
366 unsigned long chan_iobase = dev->iobase + chan * 8;
368 inw(chan_iobase + REG_C0M); /* Is this read required? */
370 /* Check what Application of Counter this channel is configured for */
371 switch (devpriv->gpct_config[chan]) {
372 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
373 /* data[0] contains the PULSE_WIDTH
374 data[1] contains the PULSE_PERIOD
375 @pre PULSE_PERIOD > PULSE_WIDTH > 0
376 The above periods must be expressed as a multiple of the
377 pulse frequency on the selected source
379 if ((data[1] <= data[0]) || !data[0])
382 /* Fall thru to write the PULSE_WIDTH */
384 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
385 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
386 outw((data[0] >> 16) & 0xffff, chan_iobase + REG_C0H);
387 outw(data[0] & 0xffff, chan_iobase + REG_C0L);
397 #define ISR_ADC_DONE 0x4
398 static int s526_ai_insn_config(struct comedi_device *dev,
399 struct comedi_subdevice *s,
400 struct comedi_insn *insn, unsigned int *data)
402 struct s526_private *devpriv = dev->private;
403 int result = -EINVAL;
410 /* data[0] : channels was set in relevant bits.
413 /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to
414 * enable channels here. The channel should be enabled in the
415 * INSN_READ handler. */
417 /* Enable ADC interrupt */
418 outw(ISR_ADC_DONE, dev->iobase + REG_IER);
419 devpriv->ai_config = (data[0] & 0x3ff) << 5;
421 devpriv->ai_config |= 0x8000; /* set the delay */
423 devpriv->ai_config |= 0x0001; /* ADC start bit */
428 static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
429 struct comedi_insn *insn, unsigned int *data)
431 struct s526_private *devpriv = dev->private;
432 unsigned int chan = CR_CHAN(insn->chanspec);
434 unsigned short value;
438 /* Set configured delay, enable channel for this channel only,
439 * select "ADC read" channel, set "ADC start" bit. */
440 value = (devpriv->ai_config & 0x8000) |
441 ((1 << 5) << chan) | (chan << 1) | 0x0001;
443 /* convert n samples */
444 for (n = 0; n < insn->n; n++) {
445 /* trigger conversion */
446 outw(value, dev->iobase + REG_ADC);
449 /* wait for conversion to end */
450 for (i = 0; i < TIMEOUT; i++) {
451 status = inw(dev->iobase + REG_ISR);
452 if (status & ISR_ADC_DONE) {
453 outw(ISR_ADC_DONE, dev->iobase + REG_ISR);
461 d = inw(dev->iobase + REG_ADD);
464 data[n] = d ^ 0x8000;
467 /* return the number of samples read/written */
471 static int s526_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
472 struct comedi_insn *insn, unsigned int *data)
474 struct s526_private *devpriv = dev->private;
475 unsigned int chan = CR_CHAN(insn->chanspec);
480 outw(val, dev->iobase + REG_DAC);
482 for (i = 0; i < insn->n; i++) {
483 outw(data[i], dev->iobase + REG_ADD);
484 devpriv->ao_readback[chan] = data[i];
485 /* starts the D/A conversion */
486 outw(val + 1, dev->iobase + REG_DAC);
492 static int s526_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
493 struct comedi_insn *insn, unsigned int *data)
495 struct s526_private *devpriv = dev->private;
496 unsigned int chan = CR_CHAN(insn->chanspec);
499 for (i = 0; i < insn->n; i++)
500 data[i] = devpriv->ao_readback[chan];
505 static int s526_dio_insn_bits(struct comedi_device *dev,
506 struct comedi_subdevice *s,
507 struct comedi_insn *insn, unsigned int *data)
510 s->state &= ~data[0];
511 s->state |= data[0] & data[1];
513 outw(s->state, dev->iobase + REG_DIO);
516 data[1] = inw(dev->iobase + REG_DIO) & 0xff;
521 static int s526_dio_insn_config(struct comedi_device *dev,
522 struct comedi_subdevice *s,
523 struct comedi_insn *insn, unsigned int *data)
525 unsigned int chan = CR_CHAN(insn->chanspec);
529 mask = 0xF << (group << 2);
531 case INSN_CONFIG_DIO_OUTPUT:
532 /* bit 10/11 set the group 1/2's mode */
533 s->state |= 1 << (group + 10);
536 case INSN_CONFIG_DIO_INPUT:
537 s->state &= ~(1 << (group + 10)); /* 1 is output, 0 is input. */
540 case INSN_CONFIG_DIO_QUERY:
541 data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT;
546 outw(s->state, dev->iobase + REG_DIO);
551 static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
553 struct s526_private *devpriv;
554 struct comedi_subdevice *s;
557 ret = comedi_request_region(dev, it->options[0], S526_IOSIZE);
561 devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
564 dev->private = devpriv;
566 ret = comedi_alloc_subdevices(dev, 4);
570 s = &dev->subdevices[0];
571 /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
572 s->type = COMEDI_SUBD_COUNTER;
573 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
575 s->maxdata = 0x00ffffff; /* 24 bit counter */
576 s->insn_read = s526_gpct_rinsn;
577 s->insn_config = s526_gpct_insn_config;
578 s->insn_write = s526_gpct_winsn;
580 s = &dev->subdevices[1];
581 /* analog input subdevice */
582 s->type = COMEDI_SUBD_AI;
583 s->subdev_flags = SDF_READABLE | SDF_DIFF;
584 /* channels 0 to 7 are the regular differential inputs */
585 /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */
588 s->range_table = &range_bipolar10;
589 s->len_chanlist = 16;
590 s->insn_read = s526_ai_rinsn;
591 s->insn_config = s526_ai_insn_config;
593 s = &dev->subdevices[2];
594 /* analog output subdevice */
595 s->type = COMEDI_SUBD_AO;
596 s->subdev_flags = SDF_WRITABLE;
599 s->range_table = &range_bipolar10;
600 s->insn_write = s526_ao_winsn;
601 s->insn_read = s526_ao_rinsn;
603 s = &dev->subdevices[3];
604 /* digital i/o subdevice */
605 s->type = COMEDI_SUBD_DIO;
606 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
609 s->range_table = &range_digital;
610 s->insn_bits = s526_dio_insn_bits;
611 s->insn_config = s526_dio_insn_config;
616 static struct comedi_driver s526_driver = {
617 .driver_name = "s526",
618 .module = THIS_MODULE,
619 .attach = s526_attach,
620 .detach = comedi_legacy_detach,
622 module_comedi_driver(s526_driver);
624 MODULE_AUTHOR("Comedi http://www.comedi.org");
625 MODULE_DESCRIPTION("Comedi low-level driver");
626 MODULE_LICENSE("GPL");