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 "../comedidev.h"
40 #include <linux/ioport.h>
41 #include <asm/byteorder.h>
45 #define S526_START_AI_CONV 0
46 #define S526_AI_READ 0
49 #define S526_IOSIZE 0x40
50 #define S526_NUM_PORTS 27
81 struct counter_mode_register_t {
82 #if defined(__LITTLE_ENDIAN_BITFIELD)
83 unsigned short coutSource:1;
84 unsigned short coutPolarity:1;
85 unsigned short autoLoadResetRcap:3;
86 unsigned short hwCtEnableSource:2;
87 unsigned short ctEnableCtrl:2;
88 unsigned short clockSource:2;
89 unsigned short countDir:1;
90 unsigned short countDirCtrl:1;
91 unsigned short outputRegLatchCtrl:1;
92 unsigned short preloadRegSel:1;
93 unsigned short reserved:1;
94 #elif defined(__BIG_ENDIAN_BITFIELD)
95 unsigned short reserved:1;
96 unsigned short preloadRegSel:1;
97 unsigned short outputRegLatchCtrl:1;
98 unsigned short countDirCtrl:1;
99 unsigned short countDir:1;
100 unsigned short clockSource:2;
101 unsigned short ctEnableCtrl:2;
102 unsigned short hwCtEnableSource:2;
103 unsigned short autoLoadResetRcap:3;
104 unsigned short coutPolarity:1;
105 unsigned short coutSource:1;
107 #error Unknown bit field order
112 struct counter_mode_register_t reg;
113 unsigned short value;
116 struct s526_private {
117 unsigned int ao_readback[2];
118 unsigned int gpct_config[4];
119 unsigned short ai_config;
122 static int s526_gpct_rinsn(struct comedi_device *dev,
123 struct comedi_subdevice *s,
124 struct comedi_insn *insn,
127 unsigned int chan = CR_CHAN(insn->chanspec);
128 unsigned long chan_iobase = dev->iobase + chan * 8;
133 for (i = 0; i < insn->n; i++) {
134 /* Read the low word first */
135 lo = inw(chan_iobase + REG_C0L) & 0xffff;
136 hi = inw(chan_iobase + REG_C0H) & 0xff;
138 data[i] = (hi << 16) | lo;
144 static int s526_gpct_insn_config(struct comedi_device *dev,
145 struct comedi_subdevice *s,
146 struct comedi_insn *insn,
149 struct s526_private *devpriv = dev->private;
150 unsigned int chan = CR_CHAN(insn->chanspec);
151 unsigned long chan_iobase = dev->iobase + chan * 8;
155 /* Check what type of Counter the user requested, data[0] contains */
156 /* the Application type */
158 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
160 data[0]: Application Type
161 data[1]: Counter Mode Register Value
162 data[2]: Pre-load Register Value
163 data[3]: Conter Control Register
165 devpriv->gpct_config[chan] = data[0];
168 /* Example of Counter Application */
169 /* One-shot (software trigger) */
170 cmReg.reg.coutSource = 0; /* out RCAP */
171 cmReg.reg.coutPolarity = 1; /* Polarity inverted */
172 cmReg.reg.autoLoadResetRcap = 0;/* Auto load disabled */
173 cmReg.reg.hwCtEnableSource = 3; /* NOT RCAP */
174 cmReg.reg.ctEnableCtrl = 2; /* Hardware */
175 cmReg.reg.clockSource = 2; /* Internal */
176 cmReg.reg.countDir = 1; /* Down */
177 cmReg.reg.countDirCtrl = 1; /* Software */
178 cmReg.reg.outputRegLatchCtrl = 0; /* latch on read */
179 cmReg.reg.preloadRegSel = 0; /* PR0 */
180 cmReg.reg.reserved = 0;
182 outw(cmReg.value, chan_iobase + REG_C0M);
184 outw(0x0001, chan_iobase + REG_C0H);
185 outw(0x3C68, chan_iobase + REG_C0L);
187 /* Reset the counter */
188 outw(0x8000, chan_iobase + REG_C0C);
189 /* Load the counter from PR0 */
190 outw(0x4000, chan_iobase + REG_C0C);
192 /* Reset RCAP (fires one-shot) */
193 outw(0x0008, chan_iobase + REG_C0C);
198 /* Set Counter Mode Register */
199 cmReg.value = data[1] & 0xffff;
200 outw(cmReg.value, chan_iobase + REG_C0M);
202 /* Reset the counter if it is software preload */
203 if (cmReg.reg.autoLoadResetRcap == 0) {
204 /* Reset the counter */
205 outw(0x8000, chan_iobase + REG_C0C);
206 /* Load the counter from PR0
207 * outw(0x4000, chan_iobase + REG_C0C);
211 /* 0 quadrature, 1 software control */
212 cmReg.reg.countDirCtrl = 0;
214 /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
215 if (data[1] == GPCT_X2)
216 cmReg.reg.clockSource = 1;
217 else if (data[1] == GPCT_X4)
218 cmReg.reg.clockSource = 2;
220 cmReg.reg.clockSource = 0;
222 /* When to take into account the indexpulse: */
223 /*if (data[2] == GPCT_IndexPhaseLowLow) {
224 } else if (data[2] == GPCT_IndexPhaseLowHigh) {
225 } else if (data[2] == GPCT_IndexPhaseHighLow) {
226 } else if (data[2] == GPCT_IndexPhaseHighHigh) {
228 /* Take into account the index pulse? */
229 if (data[3] == GPCT_RESET_COUNTER_ON_INDEX)
230 /* Auto load with INDEX^ */
231 cmReg.reg.autoLoadResetRcap = 4;
233 /* Set Counter Mode Register */
234 cmReg.value = data[1] & 0xffff;
235 outw(cmReg.value, chan_iobase + REG_C0M);
237 /* Load the pre-load register high word */
238 val = (data[2] >> 16) & 0xffff;
239 outw(val, chan_iobase + REG_C0H);
241 /* Load the pre-load register low word */
242 val = data[2] & 0xffff;
243 outw(val, chan_iobase + REG_C0L);
245 /* Write the Counter Control Register */
247 val = data[3] & 0xffff;
248 outw(val, chan_iobase + REG_C0C);
250 /* Reset the counter if it is software preload */
251 if (cmReg.reg.autoLoadResetRcap == 0) {
252 /* Reset the counter */
253 outw(0x8000, chan_iobase + REG_C0C);
254 /* Load the counter from PR0 */
255 outw(0x4000, chan_iobase + REG_C0C);
260 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
262 data[0]: Application Type
263 data[1]: Counter Mode Register Value
264 data[2]: Pre-load Register 0 Value
265 data[3]: Pre-load Register 1 Value
266 data[4]: Conter Control Register
268 devpriv->gpct_config[chan] = data[0];
270 /* Set Counter Mode Register */
271 cmReg.value = data[1] & 0xffff;
272 cmReg.reg.preloadRegSel = 0; /* PR0 */
273 outw(cmReg.value, chan_iobase + REG_C0M);
275 /* Load the pre-load register 0 high word */
276 val = (data[2] >> 16) & 0xffff;
277 outw(val, chan_iobase + REG_C0H);
279 /* Load the pre-load register 0 low word */
280 val = data[2] & 0xffff;
281 outw(val, chan_iobase + REG_C0L);
283 /* Set Counter Mode Register */
284 cmReg.value = data[1] & 0xffff;
285 cmReg.reg.preloadRegSel = 1; /* PR1 */
286 outw(cmReg.value, chan_iobase + REG_C0M);
288 /* Load the pre-load register 1 high word */
289 val = (data[3] >> 16) & 0xffff;
290 outw(val, chan_iobase + REG_C0H);
292 /* Load the pre-load register 1 low word */
293 val = data[3] & 0xffff;
294 outw(val, chan_iobase + REG_C0L);
296 /* Write the Counter Control Register */
298 val = data[4] & 0xffff;
299 outw(val, chan_iobase + REG_C0C);
303 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
305 data[0]: Application Type
306 data[1]: Counter Mode Register Value
307 data[2]: Pre-load Register 0 Value
308 data[3]: Pre-load Register 1 Value
309 data[4]: Conter Control Register
311 devpriv->gpct_config[chan] = data[0];
313 /* Set Counter Mode Register */
314 cmReg.value = data[1] & 0xffff;
315 cmReg.reg.preloadRegSel = 0; /* PR0 */
316 outw(cmReg.value, chan_iobase + REG_C0M);
318 /* Load the pre-load register 0 high word */
319 val = (data[2] >> 16) & 0xffff;
320 outw(val, chan_iobase + REG_C0H);
322 /* Load the pre-load register 0 low word */
323 val = data[2] & 0xffff;
324 outw(val, chan_iobase + REG_C0L);
326 /* Set Counter Mode Register */
327 cmReg.value = data[1] & 0xffff;
328 cmReg.reg.preloadRegSel = 1; /* PR1 */
329 outw(cmReg.value, chan_iobase + REG_C0M);
331 /* Load the pre-load register 1 high word */
332 val = (data[3] >> 16) & 0xffff;
333 outw(val, chan_iobase + REG_C0H);
335 /* Load the pre-load register 1 low word */
336 val = data[3] & 0xffff;
337 outw(val, chan_iobase + REG_C0L);
339 /* Write the Counter Control Register */
341 val = data[4] & 0xffff;
342 outw(val, chan_iobase + REG_C0C);
354 static int s526_gpct_winsn(struct comedi_device *dev,
355 struct comedi_subdevice *s,
356 struct comedi_insn *insn,
359 struct s526_private *devpriv = dev->private;
360 unsigned int chan = CR_CHAN(insn->chanspec);
361 unsigned long chan_iobase = dev->iobase + chan * 8;
363 inw(chan_iobase + REG_C0M); /* Is this read required? */
365 /* Check what Application of Counter this channel is configured for */
366 switch (devpriv->gpct_config[chan]) {
367 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
368 /* data[0] contains the PULSE_WIDTH
369 data[1] contains the PULSE_PERIOD
370 @pre PULSE_PERIOD > PULSE_WIDTH > 0
371 The above periods must be expressed as a multiple of the
372 pulse frequency on the selected source
374 if ((data[1] <= data[0]) || !data[0])
377 /* Fall thru to write the PULSE_WIDTH */
379 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
380 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
381 outw((data[0] >> 16) & 0xffff, chan_iobase + REG_C0H);
382 outw(data[0] & 0xffff, chan_iobase + REG_C0L);
392 #define ISR_ADC_DONE 0x4
393 static int s526_ai_insn_config(struct comedi_device *dev,
394 struct comedi_subdevice *s,
395 struct comedi_insn *insn, unsigned int *data)
397 struct s526_private *devpriv = dev->private;
398 int result = -EINVAL;
405 /* data[0] : channels was set in relevant bits.
408 /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to
409 * enable channels here. The channel should be enabled in the
410 * INSN_READ handler. */
412 /* Enable ADC interrupt */
413 outw(ISR_ADC_DONE, dev->iobase + REG_IER);
414 devpriv->ai_config = (data[0] & 0x3ff) << 5;
416 devpriv->ai_config |= 0x8000; /* set the delay */
418 devpriv->ai_config |= 0x0001; /* ADC start bit */
423 static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
424 struct comedi_insn *insn, unsigned int *data)
426 struct s526_private *devpriv = dev->private;
427 unsigned int chan = CR_CHAN(insn->chanspec);
429 unsigned short value;
433 /* Set configured delay, enable channel for this channel only,
434 * select "ADC read" channel, set "ADC start" bit. */
435 value = (devpriv->ai_config & 0x8000) |
436 ((1 << 5) << chan) | (chan << 1) | 0x0001;
438 /* convert n samples */
439 for (n = 0; n < insn->n; n++) {
440 /* trigger conversion */
441 outw(value, dev->iobase + REG_ADC);
444 /* wait for conversion to end */
445 for (i = 0; i < TIMEOUT; i++) {
446 status = inw(dev->iobase + REG_ISR);
447 if (status & ISR_ADC_DONE) {
448 outw(ISR_ADC_DONE, dev->iobase + REG_ISR);
456 d = inw(dev->iobase + REG_ADD);
459 data[n] = d ^ 0x8000;
462 /* return the number of samples read/written */
466 static int s526_ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
467 struct comedi_insn *insn, unsigned int *data)
469 struct s526_private *devpriv = dev->private;
470 unsigned int chan = CR_CHAN(insn->chanspec);
475 outw(val, dev->iobase + REG_DAC);
477 for (i = 0; i < insn->n; i++) {
478 outw(data[i], dev->iobase + REG_ADD);
479 devpriv->ao_readback[chan] = data[i];
480 /* starts the D/A conversion */
481 outw(val + 1, dev->iobase + REG_DAC);
487 static int s526_ao_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
488 struct comedi_insn *insn, unsigned int *data)
490 struct s526_private *devpriv = dev->private;
491 unsigned int chan = CR_CHAN(insn->chanspec);
494 for (i = 0; i < insn->n; i++)
495 data[i] = devpriv->ao_readback[chan];
500 static int s526_dio_insn_bits(struct comedi_device *dev,
501 struct comedi_subdevice *s,
502 struct comedi_insn *insn, unsigned int *data)
505 s->state &= ~data[0];
506 s->state |= data[0] & data[1];
508 outw(s->state, dev->iobase + REG_DIO);
511 data[1] = inw(dev->iobase + REG_DIO) & 0xff;
516 static int s526_dio_insn_config(struct comedi_device *dev,
517 struct comedi_subdevice *s,
518 struct comedi_insn *insn, unsigned int *data)
520 unsigned int chan = CR_CHAN(insn->chanspec);
524 mask = 0xF << (group << 2);
526 case INSN_CONFIG_DIO_OUTPUT:
527 /* bit 10/11 set the group 1/2's mode */
528 s->state |= 1 << (group + 10);
531 case INSN_CONFIG_DIO_INPUT:
532 s->state &= ~(1 << (group + 10)); /* 1 is output, 0 is input. */
535 case INSN_CONFIG_DIO_QUERY:
536 data[1] = (s->io_bits & mask) ? COMEDI_OUTPUT : COMEDI_INPUT;
541 outw(s->state, dev->iobase + REG_DIO);
546 static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
548 struct s526_private *devpriv;
549 struct comedi_subdevice *s;
552 ret = comedi_request_region(dev, it->options[0], S526_IOSIZE);
556 devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL);
559 dev->private = devpriv;
561 ret = comedi_alloc_subdevices(dev, 4);
565 s = &dev->subdevices[0];
566 /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
567 s->type = COMEDI_SUBD_COUNTER;
568 s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_LSAMPL;
570 s->maxdata = 0x00ffffff; /* 24 bit counter */
571 s->insn_read = s526_gpct_rinsn;
572 s->insn_config = s526_gpct_insn_config;
573 s->insn_write = s526_gpct_winsn;
575 s = &dev->subdevices[1];
576 /* analog input subdevice */
577 s->type = COMEDI_SUBD_AI;
578 s->subdev_flags = SDF_READABLE | SDF_DIFF;
579 /* channels 0 to 7 are the regular differential inputs */
580 /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */
583 s->range_table = &range_bipolar10;
584 s->len_chanlist = 16;
585 s->insn_read = s526_ai_rinsn;
586 s->insn_config = s526_ai_insn_config;
588 s = &dev->subdevices[2];
589 /* analog output subdevice */
590 s->type = COMEDI_SUBD_AO;
591 s->subdev_flags = SDF_WRITABLE;
594 s->range_table = &range_bipolar10;
595 s->insn_write = s526_ao_winsn;
596 s->insn_read = s526_ao_rinsn;
598 s = &dev->subdevices[3];
599 /* digital i/o subdevice */
600 s->type = COMEDI_SUBD_DIO;
601 s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
604 s->range_table = &range_digital;
605 s->insn_bits = s526_dio_insn_bits;
606 s->insn_config = s526_dio_insn_config;
611 static struct comedi_driver s526_driver = {
612 .driver_name = "s526",
613 .module = THIS_MODULE,
614 .attach = s526_attach,
615 .detach = comedi_legacy_detach,
617 module_comedi_driver(s526_driver);
619 MODULE_AUTHOR("Comedi http://www.comedi.org");
620 MODULE_DESCRIPTION("Comedi low-level driver");
621 MODULE_LICENSE("GPL");