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 gpct_config
[4];
115 unsigned short ai_config
;
118 static int s526_gpct_rinsn(struct comedi_device
*dev
,
119 struct comedi_subdevice
*s
,
120 struct comedi_insn
*insn
,
123 unsigned int chan
= CR_CHAN(insn
->chanspec
);
124 unsigned long chan_iobase
= dev
->iobase
+ chan
* 8;
129 for (i
= 0; i
< insn
->n
; i
++) {
130 /* Read the low word first */
131 lo
= inw(chan_iobase
+ REG_C0L
) & 0xffff;
132 hi
= inw(chan_iobase
+ REG_C0H
) & 0xff;
134 data
[i
] = (hi
<< 16) | lo
;
140 static int s526_gpct_insn_config(struct comedi_device
*dev
,
141 struct comedi_subdevice
*s
,
142 struct comedi_insn
*insn
,
145 struct s526_private
*devpriv
= dev
->private;
146 unsigned int chan
= CR_CHAN(insn
->chanspec
);
147 unsigned long chan_iobase
= dev
->iobase
+ chan
* 8;
151 /* Check what type of Counter the user requested, data[0] contains */
152 /* the Application type */
154 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER
:
156 data[0]: Application Type
157 data[1]: Counter Mode Register Value
158 data[2]: Pre-load Register Value
159 data[3]: Conter Control Register
161 devpriv
->gpct_config
[chan
] = data
[0];
164 /* Example of Counter Application */
165 /* One-shot (software trigger) */
166 cmReg
.reg
.coutSource
= 0; /* out RCAP */
167 cmReg
.reg
.coutPolarity
= 1; /* Polarity inverted */
168 cmReg
.reg
.autoLoadResetRcap
= 0;/* Auto load disabled */
169 cmReg
.reg
.hwCtEnableSource
= 3; /* NOT RCAP */
170 cmReg
.reg
.ctEnableCtrl
= 2; /* Hardware */
171 cmReg
.reg
.clockSource
= 2; /* Internal */
172 cmReg
.reg
.countDir
= 1; /* Down */
173 cmReg
.reg
.countDirCtrl
= 1; /* Software */
174 cmReg
.reg
.outputRegLatchCtrl
= 0; /* latch on read */
175 cmReg
.reg
.preloadRegSel
= 0; /* PR0 */
176 cmReg
.reg
.reserved
= 0;
178 outw(cmReg
.value
, chan_iobase
+ REG_C0M
);
180 outw(0x0001, chan_iobase
+ REG_C0H
);
181 outw(0x3C68, chan_iobase
+ REG_C0L
);
183 /* Reset the counter */
184 outw(0x8000, chan_iobase
+ REG_C0C
);
185 /* Load the counter from PR0 */
186 outw(0x4000, chan_iobase
+ REG_C0C
);
188 /* Reset RCAP (fires one-shot) */
189 outw(0x0008, chan_iobase
+ REG_C0C
);
194 /* Set Counter Mode Register */
195 cmReg
.value
= data
[1] & 0xffff;
196 outw(cmReg
.value
, chan_iobase
+ REG_C0M
);
198 /* Reset the counter if it is software preload */
199 if (cmReg
.reg
.autoLoadResetRcap
== 0) {
200 /* Reset the counter */
201 outw(0x8000, chan_iobase
+ REG_C0C
);
202 /* Load the counter from PR0
203 * outw(0x4000, chan_iobase + REG_C0C);
207 /* 0 quadrature, 1 software control */
208 cmReg
.reg
.countDirCtrl
= 0;
210 /* data[1] contains GPCT_X1, GPCT_X2 or GPCT_X4 */
211 if (data
[1] == GPCT_X2
)
212 cmReg
.reg
.clockSource
= 1;
213 else if (data
[1] == GPCT_X4
)
214 cmReg
.reg
.clockSource
= 2;
216 cmReg
.reg
.clockSource
= 0;
218 /* When to take into account the indexpulse: */
219 /*if (data[2] == GPCT_IndexPhaseLowLow) {
220 } else if (data[2] == GPCT_IndexPhaseLowHigh) {
221 } else if (data[2] == GPCT_IndexPhaseHighLow) {
222 } else if (data[2] == GPCT_IndexPhaseHighHigh) {
224 /* Take into account the index pulse? */
225 if (data
[3] == GPCT_RESET_COUNTER_ON_INDEX
)
226 /* Auto load with INDEX^ */
227 cmReg
.reg
.autoLoadResetRcap
= 4;
229 /* Set Counter Mode Register */
230 cmReg
.value
= data
[1] & 0xffff;
231 outw(cmReg
.value
, chan_iobase
+ REG_C0M
);
233 /* Load the pre-load register high word */
234 val
= (data
[2] >> 16) & 0xffff;
235 outw(val
, chan_iobase
+ REG_C0H
);
237 /* Load the pre-load register low word */
238 val
= data
[2] & 0xffff;
239 outw(val
, chan_iobase
+ REG_C0L
);
241 /* Write the Counter Control Register */
243 val
= data
[3] & 0xffff;
244 outw(val
, chan_iobase
+ REG_C0C
);
246 /* Reset the counter if it is software preload */
247 if (cmReg
.reg
.autoLoadResetRcap
== 0) {
248 /* Reset the counter */
249 outw(0x8000, chan_iobase
+ REG_C0C
);
250 /* Load the counter from PR0 */
251 outw(0x4000, chan_iobase
+ REG_C0C
);
256 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR
:
258 data[0]: Application Type
259 data[1]: Counter Mode Register Value
260 data[2]: Pre-load Register 0 Value
261 data[3]: Pre-load Register 1 Value
262 data[4]: Conter Control Register
264 devpriv
->gpct_config
[chan
] = data
[0];
266 /* Set Counter Mode Register */
267 cmReg
.value
= data
[1] & 0xffff;
268 cmReg
.reg
.preloadRegSel
= 0; /* PR0 */
269 outw(cmReg
.value
, chan_iobase
+ REG_C0M
);
271 /* Load the pre-load register 0 high word */
272 val
= (data
[2] >> 16) & 0xffff;
273 outw(val
, chan_iobase
+ REG_C0H
);
275 /* Load the pre-load register 0 low word */
276 val
= data
[2] & 0xffff;
277 outw(val
, chan_iobase
+ REG_C0L
);
279 /* Set Counter Mode Register */
280 cmReg
.value
= data
[1] & 0xffff;
281 cmReg
.reg
.preloadRegSel
= 1; /* PR1 */
282 outw(cmReg
.value
, chan_iobase
+ REG_C0M
);
284 /* Load the pre-load register 1 high word */
285 val
= (data
[3] >> 16) & 0xffff;
286 outw(val
, chan_iobase
+ REG_C0H
);
288 /* Load the pre-load register 1 low word */
289 val
= data
[3] & 0xffff;
290 outw(val
, chan_iobase
+ REG_C0L
);
292 /* Write the Counter Control Register */
294 val
= data
[4] & 0xffff;
295 outw(val
, chan_iobase
+ REG_C0C
);
299 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR
:
301 data[0]: Application Type
302 data[1]: Counter Mode Register Value
303 data[2]: Pre-load Register 0 Value
304 data[3]: Pre-load Register 1 Value
305 data[4]: Conter Control Register
307 devpriv
->gpct_config
[chan
] = data
[0];
309 /* Set Counter Mode Register */
310 cmReg
.value
= data
[1] & 0xffff;
311 cmReg
.reg
.preloadRegSel
= 0; /* PR0 */
312 outw(cmReg
.value
, chan_iobase
+ REG_C0M
);
314 /* Load the pre-load register 0 high word */
315 val
= (data
[2] >> 16) & 0xffff;
316 outw(val
, chan_iobase
+ REG_C0H
);
318 /* Load the pre-load register 0 low word */
319 val
= data
[2] & 0xffff;
320 outw(val
, chan_iobase
+ REG_C0L
);
322 /* Set Counter Mode Register */
323 cmReg
.value
= data
[1] & 0xffff;
324 cmReg
.reg
.preloadRegSel
= 1; /* PR1 */
325 outw(cmReg
.value
, chan_iobase
+ REG_C0M
);
327 /* Load the pre-load register 1 high word */
328 val
= (data
[3] >> 16) & 0xffff;
329 outw(val
, chan_iobase
+ REG_C0H
);
331 /* Load the pre-load register 1 low word */
332 val
= data
[3] & 0xffff;
333 outw(val
, chan_iobase
+ REG_C0L
);
335 /* Write the Counter Control Register */
337 val
= data
[4] & 0xffff;
338 outw(val
, chan_iobase
+ REG_C0C
);
349 static int s526_gpct_winsn(struct comedi_device
*dev
,
350 struct comedi_subdevice
*s
,
351 struct comedi_insn
*insn
,
354 struct s526_private
*devpriv
= dev
->private;
355 unsigned int chan
= CR_CHAN(insn
->chanspec
);
356 unsigned long chan_iobase
= dev
->iobase
+ chan
* 8;
358 inw(chan_iobase
+ REG_C0M
); /* Is this read required? */
360 /* Check what Application of Counter this channel is configured for */
361 switch (devpriv
->gpct_config
[chan
]) {
362 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR
:
363 /* data[0] contains the PULSE_WIDTH
364 data[1] contains the PULSE_PERIOD
365 @pre PULSE_PERIOD > PULSE_WIDTH > 0
366 The above periods must be expressed as a multiple of the
367 pulse frequency on the selected source
369 if ((data
[1] <= data
[0]) || !data
[0])
372 /* Fall thru to write the PULSE_WIDTH */
374 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER
:
375 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR
:
376 outw((data
[0] >> 16) & 0xffff, chan_iobase
+ REG_C0H
);
377 outw(data
[0] & 0xffff, chan_iobase
+ REG_C0L
);
387 #define ISR_ADC_DONE 0x4
388 static int s526_ai_insn_config(struct comedi_device
*dev
,
389 struct comedi_subdevice
*s
,
390 struct comedi_insn
*insn
, unsigned int *data
)
392 struct s526_private
*devpriv
= dev
->private;
393 int result
= -EINVAL
;
400 /* data[0] : channels was set in relevant bits.
403 /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to
404 * enable channels here. The channel should be enabled in the
405 * INSN_READ handler. */
407 /* Enable ADC interrupt */
408 outw(ISR_ADC_DONE
, dev
->iobase
+ REG_IER
);
409 devpriv
->ai_config
= (data
[0] & 0x3ff) << 5;
411 devpriv
->ai_config
|= 0x8000; /* set the delay */
413 devpriv
->ai_config
|= 0x0001; /* ADC start bit */
418 static int s526_ai_eoc(struct comedi_device
*dev
,
419 struct comedi_subdevice
*s
,
420 struct comedi_insn
*insn
,
421 unsigned long context
)
425 status
= inw(dev
->iobase
+ REG_ISR
);
426 if (status
& ISR_ADC_DONE
)
431 static int s526_ai_rinsn(struct comedi_device
*dev
, struct comedi_subdevice
*s
,
432 struct comedi_insn
*insn
, unsigned int *data
)
434 struct s526_private
*devpriv
= dev
->private;
435 unsigned int chan
= CR_CHAN(insn
->chanspec
);
437 unsigned short value
;
441 /* Set configured delay, enable channel for this channel only,
442 * select "ADC read" channel, set "ADC start" bit. */
443 value
= (devpriv
->ai_config
& 0x8000) |
444 ((1 << 5) << chan
) | (chan
<< 1) | 0x0001;
446 /* convert n samples */
447 for (n
= 0; n
< insn
->n
; n
++) {
448 /* trigger conversion */
449 outw(value
, dev
->iobase
+ REG_ADC
);
451 /* wait for conversion to end */
452 ret
= comedi_timeout(dev
, s
, insn
, s526_ai_eoc
, 0);
456 outw(ISR_ADC_DONE
, dev
->iobase
+ REG_ISR
);
459 d
= inw(dev
->iobase
+ REG_ADD
);
462 data
[n
] = d
^ 0x8000;
465 /* return the number of samples read/written */
469 static int s526_ao_insn_write(struct comedi_device
*dev
,
470 struct comedi_subdevice
*s
,
471 struct comedi_insn
*insn
,
474 unsigned int chan
= CR_CHAN(insn
->chanspec
);
475 unsigned int val
= s
->readback
[chan
];
478 outw(chan
<< 1, dev
->iobase
+ REG_DAC
);
480 for (i
= 0; i
< insn
->n
; i
++) {
482 outw(val
, dev
->iobase
+ REG_ADD
);
483 /* starts the D/A conversion */
484 outw((chan
<< 1) | 1, dev
->iobase
+ REG_DAC
);
486 s
->readback
[chan
] = val
;
491 static int s526_dio_insn_bits(struct comedi_device
*dev
,
492 struct comedi_subdevice
*s
,
493 struct comedi_insn
*insn
,
496 if (comedi_dio_update_state(s
, data
))
497 outw(s
->state
, dev
->iobase
+ REG_DIO
);
499 data
[1] = inw(dev
->iobase
+ REG_DIO
) & 0xff;
504 static int s526_dio_insn_config(struct comedi_device
*dev
,
505 struct comedi_subdevice
*s
,
506 struct comedi_insn
*insn
,
509 unsigned int chan
= CR_CHAN(insn
->chanspec
);
518 ret
= comedi_dio_insn_config(dev
, s
, insn
, data
, mask
);
522 /* bit 10/11 set the group 1/2's mode */
523 if (s
->io_bits
& 0x0f)
524 s
->state
|= (1 << 10);
526 s
->state
&= ~(1 << 10);
527 if (s
->io_bits
& 0xf0)
528 s
->state
|= (1 << 11);
530 s
->state
&= ~(1 << 11);
532 outw(s
->state
, dev
->iobase
+ REG_DIO
);
537 static int s526_attach(struct comedi_device
*dev
, struct comedi_devconfig
*it
)
539 struct s526_private
*devpriv
;
540 struct comedi_subdevice
*s
;
543 ret
= comedi_request_region(dev
, it
->options
[0], 0x40);
547 devpriv
= comedi_alloc_devpriv(dev
, sizeof(*devpriv
));
551 ret
= comedi_alloc_subdevices(dev
, 4);
555 s
= &dev
->subdevices
[0];
556 /* GENERAL-PURPOSE COUNTER/TIME (GPCT) */
557 s
->type
= COMEDI_SUBD_COUNTER
;
558 s
->subdev_flags
= SDF_READABLE
| SDF_WRITABLE
| SDF_LSAMPL
;
560 s
->maxdata
= 0x00ffffff; /* 24 bit counter */
561 s
->insn_read
= s526_gpct_rinsn
;
562 s
->insn_config
= s526_gpct_insn_config
;
563 s
->insn_write
= s526_gpct_winsn
;
565 s
= &dev
->subdevices
[1];
566 /* analog input subdevice */
567 s
->type
= COMEDI_SUBD_AI
;
568 s
->subdev_flags
= SDF_READABLE
| SDF_DIFF
;
569 /* channels 0 to 7 are the regular differential inputs */
570 /* channel 8 is "reference 0" (+10V), channel 9 is "reference 1" (0V) */
573 s
->range_table
= &range_bipolar10
;
574 s
->len_chanlist
= 16;
575 s
->insn_read
= s526_ai_rinsn
;
576 s
->insn_config
= s526_ai_insn_config
;
578 s
= &dev
->subdevices
[2];
579 /* analog output subdevice */
580 s
->type
= COMEDI_SUBD_AO
;
581 s
->subdev_flags
= SDF_WRITABLE
;
584 s
->range_table
= &range_bipolar10
;
585 s
->insn_write
= s526_ao_insn_write
;
586 s
->insn_read
= comedi_readback_insn_read
;
588 ret
= comedi_alloc_subdev_readback(s
);
592 s
= &dev
->subdevices
[3];
593 /* digital i/o subdevice */
594 s
->type
= COMEDI_SUBD_DIO
;
595 s
->subdev_flags
= SDF_READABLE
| SDF_WRITABLE
;
598 s
->range_table
= &range_digital
;
599 s
->insn_bits
= s526_dio_insn_bits
;
600 s
->insn_config
= s526_dio_insn_config
;
605 static struct comedi_driver s526_driver
= {
606 .driver_name
= "s526",
607 .module
= THIS_MODULE
,
608 .attach
= s526_attach
,
609 .detach
= comedi_legacy_detach
,
611 module_comedi_driver(s526_driver
);
613 MODULE_AUTHOR("Comedi http://www.comedi.org");
614 MODULE_DESCRIPTION("Comedi low-level driver");
615 MODULE_LICENSE("GPL");