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.
21 * Description: Sensoray 526 driver
22 * Devices: [Sensoray] 526 (s526)
24 * Everett Wang <everett.wang@everteq.com>
25 * Updated: Thu, 14 Sep. 2006
26 * Status: experimental
32 * Commands are not supported yet.
34 * Configuration Options:
35 * [0] - I/O port base address
38 #include <linux/module.h>
39 #include "../comedidev.h"
40 #include <asm/byteorder.h>
45 #define S526_TIMER_REG 0x00
46 #define S526_TIMER_LOAD(x) (((x) & 0xff) << 8)
47 #define S526_TIMER_MODE ((x) << 1)
48 #define S526_TIMER_MANUAL S526_TIMER_MODE(0)
49 #define S526_TIMER_AUTO S526_TIMER_MODE(1)
50 #define S526_TIMER_RESTART BIT(0)
51 #define S526_WDOG_REG 0x02
52 #define S526_WDOG_INVERTED BIT(4)
53 #define S526_WDOG_ENA BIT(3)
54 #define S526_WDOG_INTERVAL(x) (((x) & 0x7) << 0)
55 #define S526_AO_CTRL_REG 0x04
56 #define S526_AO_CTRL_RESET BIT(3)
57 #define S526_AO_CTRL_CHAN(x) (((x) & 0x3) << 1)
58 #define S526_AO_CTRL_START BIT(0)
59 #define S526_AI_CTRL_REG 0x06
60 #define S526_AI_CTRL_DELAY BIT(15)
61 #define S526_AI_CTRL_CONV(x) (1 << (5 + ((x) & 0x9)))
62 #define S526_AI_CTRL_READ(x) (((x) & 0xf) << 1)
63 #define S526_AI_CTRL_START BIT(0)
64 #define S526_AO_REG 0x08
65 #define S526_AI_REG 0x08
66 #define S526_DIO_CTRL_REG 0x0a
67 #define S526_DIO_CTRL_DIO3_NEG BIT(15) /* irq on DIO3 neg/pos edge */
68 #define S526_DIO_CTRL_DIO2_NEG BIT(14) /* irq on DIO2 neg/pos edge */
69 #define S526_DIO_CTRL_DIO1_NEG BIT(13) /* irq on DIO1 neg/pos edge */
70 #define S526_DIO_CTRL_DIO0_NEG BIT(12) /* irq on DIO0 neg/pos edge */
71 #define S526_DIO_CTRL_GRP2_OUT BIT(11)
72 #define S526_DIO_CTRL_GRP1_OUT BIT(10)
73 #define S526_DIO_CTRL_GRP2_NEG BIT(8) /* irq on DIO[4-7] neg/pos edge */
74 #define S526_INT_ENA_REG 0x0c
75 #define S526_INT_STATUS_REG 0x0e
76 #define S526_INT_DIO(x) BIT(8 + ((x) & 0x7))
77 #define S526_INT_EEPROM BIT(7) /* status only */
78 #define S526_INT_CNTR(x) BIT(3 + (3 - ((x) & 0x3)))
79 #define S526_INT_AI BIT(2)
80 #define S526_INT_AO BIT(1)
81 #define S526_INT_TIMER BIT(0)
82 #define S526_MISC_REG 0x10
83 #define S526_MISC_LED_OFF BIT(0)
84 #define S526_GPCT_LSB_REG(x) (0x12 + ((x) * 8))
85 #define S526_GPCT_MSB_REG(x) (0x14 + ((x) * 8))
86 #define S526_GPCT_MODE_REG(x) (0x16 + ((x) * 8))
87 #define S526_GPCT_CTRL_REG(x) (0x18 + ((x) * 8))
88 #define S526_EEPROM_DATA_REG 0x32
89 #define S526_EEPROM_CTRL_REG 0x34
90 #define S526_EEPROM_CTRL_ADDR(x) (((x) & 0x3f) << 3)
91 #define S526_EEPROM_CTRL(x) (((x) & 0x3) << 1)
92 #define S526_EEPROM_CTRL_READ S526_EEPROM_CTRL(2)
93 #define S526_EEPROM_CTRL_START BIT(0)
95 struct counter_mode_register_t
{
96 #if defined(__LITTLE_ENDIAN_BITFIELD)
97 unsigned short coutSource
:1;
98 unsigned short coutPolarity
:1;
99 unsigned short autoLoadResetRcap
:3;
100 unsigned short hwCtEnableSource
:2;
101 unsigned short ctEnableCtrl
:2;
102 unsigned short clockSource
:2;
103 unsigned short countDir
:1;
104 unsigned short countDirCtrl
:1;
105 unsigned short outputRegLatchCtrl
:1;
106 unsigned short preloadRegSel
:1;
107 unsigned short reserved
:1;
108 #elif defined(__BIG_ENDIAN_BITFIELD)
109 unsigned short reserved
:1;
110 unsigned short preloadRegSel
:1;
111 unsigned short outputRegLatchCtrl
:1;
112 unsigned short countDirCtrl
:1;
113 unsigned short countDir
:1;
114 unsigned short clockSource
:2;
115 unsigned short ctEnableCtrl
:2;
116 unsigned short hwCtEnableSource
:2;
117 unsigned short autoLoadResetRcap
:3;
118 unsigned short coutPolarity
:1;
119 unsigned short coutSource
:1;
121 #error Unknown bit field order
126 struct counter_mode_register_t reg
;
127 unsigned short value
;
130 struct s526_private
{
131 unsigned int gpct_config
[4];
132 unsigned short ai_ctrl
;
135 static void s526_gpct_write(struct comedi_device
*dev
,
136 unsigned int chan
, unsigned int val
)
138 /* write high word then low word */
139 outw((val
>> 16) & 0xffff, dev
->iobase
+ S526_GPCT_MSB_REG(chan
));
140 outw(val
& 0xffff, dev
->iobase
+ S526_GPCT_LSB_REG(chan
));
143 static unsigned int s526_gpct_read(struct comedi_device
*dev
,
148 /* read the low word then high word */
149 val
= inw(dev
->iobase
+ S526_GPCT_LSB_REG(chan
)) & 0xffff;
150 val
|= (inw(dev
->iobase
+ S526_GPCT_MSB_REG(chan
)) & 0xff) << 16;
155 static int s526_gpct_rinsn(struct comedi_device
*dev
,
156 struct comedi_subdevice
*s
,
157 struct comedi_insn
*insn
,
160 unsigned int chan
= CR_CHAN(insn
->chanspec
);
163 for (i
= 0; i
< insn
->n
; i
++)
164 data
[i
] = s526_gpct_read(dev
, chan
);
169 static int s526_gpct_insn_config(struct comedi_device
*dev
,
170 struct comedi_subdevice
*s
,
171 struct comedi_insn
*insn
,
174 struct s526_private
*devpriv
= dev
->private;
175 unsigned int chan
= CR_CHAN(insn
->chanspec
);
180 * Check what type of Counter the user requested
181 * data[0] contains the Application type
184 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER
:
186 * data[0]: Application Type
187 * data[1]: Counter Mode Register Value
188 * data[2]: Pre-load Register Value
189 * data[3]: Conter Control Register
191 devpriv
->gpct_config
[chan
] = data
[0];
194 /* Set Counter Mode Register */
195 cmReg
.value
= data
[1] & 0xffff;
196 outw(cmReg
.value
, dev
->iobase
+ S526_GPCT_MODE_REG(chan
));
198 /* Reset the counter if it is software preload */
199 if (cmReg
.reg
.autoLoadResetRcap
== 0) {
200 /* Reset the counter */
201 outw(0x8000, dev
->iobase
+ S526_GPCT_CTRL_REG(chan
));
202 /* Load the counter from PR0
203 * outw(0x4000, dev->iobase + S526_GPCT_CTRL_REG(chan));
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: */
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) {
226 /* Take into account the index pulse? */
227 if (data
[3] == GPCT_RESET_COUNTER_ON_INDEX
)
228 /* Auto load with INDEX^ */
229 cmReg
.reg
.autoLoadResetRcap
= 4;
231 /* Set Counter Mode Register */
232 cmReg
.value
= data
[1] & 0xffff;
233 outw(cmReg
.value
, dev
->iobase
+ S526_GPCT_MODE_REG(chan
));
235 /* Load the pre-load register */
236 s526_gpct_write(dev
, chan
, data
[2]);
238 /* Write the Counter Control Register */
240 outw(data
[3] & 0xffff,
241 dev
->iobase
+ S526_GPCT_CTRL_REG(chan
));
243 /* Reset the counter if it is software preload */
244 if (cmReg
.reg
.autoLoadResetRcap
== 0) {
245 /* Reset the counter */
246 outw(0x8000, dev
->iobase
+ S526_GPCT_CTRL_REG(chan
));
247 /* Load the counter from PR0 */
248 outw(0x4000, dev
->iobase
+ S526_GPCT_CTRL_REG(chan
));
253 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR
:
255 * data[0]: Application Type
256 * data[1]: Counter Mode Register Value
257 * data[2]: Pre-load Register 0 Value
258 * data[3]: Pre-load Register 1 Value
259 * data[4]: Conter Control Register
261 devpriv
->gpct_config
[chan
] = data
[0];
263 /* Set Counter Mode Register */
264 cmReg
.value
= data
[1] & 0xffff;
265 cmReg
.reg
.preloadRegSel
= 0; /* PR0 */
266 outw(cmReg
.value
, dev
->iobase
+ S526_GPCT_MODE_REG(chan
));
268 /* Load the pre-load register 0 */
269 s526_gpct_write(dev
, chan
, data
[2]);
271 /* Set Counter Mode Register */
272 cmReg
.value
= data
[1] & 0xffff;
273 cmReg
.reg
.preloadRegSel
= 1; /* PR1 */
274 outw(cmReg
.value
, dev
->iobase
+ S526_GPCT_MODE_REG(chan
));
276 /* Load the pre-load register 1 */
277 s526_gpct_write(dev
, chan
, data
[3]);
279 /* Write the Counter Control Register */
281 val
= data
[4] & 0xffff;
282 outw(val
, dev
->iobase
+ S526_GPCT_CTRL_REG(chan
));
286 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR
:
288 * data[0]: Application Type
289 * data[1]: Counter Mode Register Value
290 * data[2]: Pre-load Register 0 Value
291 * data[3]: Pre-load Register 1 Value
292 * data[4]: Conter Control Register
294 devpriv
->gpct_config
[chan
] = data
[0];
296 /* Set Counter Mode Register */
297 cmReg
.value
= data
[1] & 0xffff;
298 cmReg
.reg
.preloadRegSel
= 0; /* PR0 */
299 outw(cmReg
.value
, dev
->iobase
+ S526_GPCT_MODE_REG(chan
));
301 /* Load the pre-load register 0 */
302 s526_gpct_write(dev
, chan
, data
[2]);
304 /* Set Counter Mode Register */
305 cmReg
.value
= data
[1] & 0xffff;
306 cmReg
.reg
.preloadRegSel
= 1; /* PR1 */
307 outw(cmReg
.value
, dev
->iobase
+ S526_GPCT_MODE_REG(chan
));
309 /* Load the pre-load register 1 */
310 s526_gpct_write(dev
, chan
, data
[3]);
312 /* Write the Counter Control Register */
314 val
= data
[4] & 0xffff;
315 outw(val
, dev
->iobase
+ S526_GPCT_CTRL_REG(chan
));
326 static int s526_gpct_winsn(struct comedi_device
*dev
,
327 struct comedi_subdevice
*s
,
328 struct comedi_insn
*insn
,
331 struct s526_private
*devpriv
= dev
->private;
332 unsigned int chan
= CR_CHAN(insn
->chanspec
);
334 inw(dev
->iobase
+ S526_GPCT_MODE_REG(chan
)); /* Is this required? */
336 /* Check what Application of Counter this channel is configured for */
337 switch (devpriv
->gpct_config
[chan
]) {
338 case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR
:
340 * data[0] contains the PULSE_WIDTH
341 * data[1] contains the PULSE_PERIOD
342 * @pre PULSE_PERIOD > PULSE_WIDTH > 0
343 * The above periods must be expressed as a multiple of the
344 * pulse frequency on the selected source
346 if ((data
[1] <= data
[0]) || !data
[0])
349 /* Fall thru to write the PULSE_WIDTH */
351 case INSN_CONFIG_GPCT_QUADRATURE_ENCODER
:
352 case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR
:
353 s526_gpct_write(dev
, chan
, data
[0]);
363 static int s526_eoc(struct comedi_device
*dev
,
364 struct comedi_subdevice
*s
,
365 struct comedi_insn
*insn
,
366 unsigned long context
)
370 status
= inw(dev
->iobase
+ S526_INT_STATUS_REG
);
371 if (status
& context
) {
372 /* we got our eoc event, clear it */
373 outw(context
, dev
->iobase
+ S526_INT_STATUS_REG
);
379 static int s526_ai_insn_read(struct comedi_device
*dev
,
380 struct comedi_subdevice
*s
,
381 struct comedi_insn
*insn
,
384 struct s526_private
*devpriv
= dev
->private;
385 unsigned int chan
= CR_CHAN(insn
->chanspec
);
391 ctrl
= S526_AI_CTRL_CONV(chan
) | S526_AI_CTRL_READ(chan
) |
393 if (ctrl
!= devpriv
->ai_ctrl
) {
395 * The multiplexor needs to change, enable the 15us
396 * delay for the first sample.
398 devpriv
->ai_ctrl
= ctrl
;
399 ctrl
|= S526_AI_CTRL_DELAY
;
402 for (i
= 0; i
< insn
->n
; i
++) {
403 /* trigger conversion */
404 outw(ctrl
, dev
->iobase
+ S526_AI_CTRL_REG
);
405 ctrl
&= ~S526_AI_CTRL_DELAY
;
407 /* wait for conversion to end */
408 ret
= comedi_timeout(dev
, s
, insn
, s526_eoc
, S526_INT_AI
);
412 val
= inw(dev
->iobase
+ S526_AI_REG
);
413 data
[i
] = comedi_offset_munge(s
, val
);
419 static int s526_ao_insn_write(struct comedi_device
*dev
,
420 struct comedi_subdevice
*s
,
421 struct comedi_insn
*insn
,
424 unsigned int chan
= CR_CHAN(insn
->chanspec
);
425 unsigned int ctrl
= S526_AO_CTRL_CHAN(chan
);
426 unsigned int val
= s
->readback
[chan
];
430 outw(ctrl
, dev
->iobase
+ S526_AO_CTRL_REG
);
431 ctrl
|= S526_AO_CTRL_START
;
433 for (i
= 0; i
< insn
->n
; i
++) {
435 outw(val
, dev
->iobase
+ S526_AO_REG
);
436 outw(ctrl
, dev
->iobase
+ S526_AO_CTRL_REG
);
438 /* wait for conversion to end */
439 ret
= comedi_timeout(dev
, s
, insn
, s526_eoc
, S526_INT_AO
);
443 s
->readback
[chan
] = val
;
448 static int s526_dio_insn_bits(struct comedi_device
*dev
,
449 struct comedi_subdevice
*s
,
450 struct comedi_insn
*insn
,
453 if (comedi_dio_update_state(s
, data
))
454 outw(s
->state
, dev
->iobase
+ S526_DIO_CTRL_REG
);
456 data
[1] = inw(dev
->iobase
+ S526_DIO_CTRL_REG
) & 0xff;
461 static int s526_dio_insn_config(struct comedi_device
*dev
,
462 struct comedi_subdevice
*s
,
463 struct comedi_insn
*insn
,
466 unsigned int chan
= CR_CHAN(insn
->chanspec
);
471 * Digital I/O can be configured as inputs or outputs in
472 * groups of 4; DIO group 1 (DIO0-3) and DIO group 2 (DIO4-7).
479 ret
= comedi_dio_insn_config(dev
, s
, insn
, data
, mask
);
483 if (s
->io_bits
& 0x0f)
484 s
->state
|= S526_DIO_CTRL_GRP1_OUT
;
486 s
->state
&= ~S526_DIO_CTRL_GRP1_OUT
;
487 if (s
->io_bits
& 0xf0)
488 s
->state
|= S526_DIO_CTRL_GRP2_OUT
;
490 s
->state
&= ~S526_DIO_CTRL_GRP2_OUT
;
492 outw(s
->state
, dev
->iobase
+ S526_DIO_CTRL_REG
);
497 static int s526_attach(struct comedi_device
*dev
, struct comedi_devconfig
*it
)
499 struct s526_private
*devpriv
;
500 struct comedi_subdevice
*s
;
503 ret
= comedi_request_region(dev
, it
->options
[0], 0x40);
507 devpriv
= comedi_alloc_devpriv(dev
, sizeof(*devpriv
));
511 ret
= comedi_alloc_subdevices(dev
, 4);
515 /* General-Purpose Counter/Timer (GPCT) */
516 s
= &dev
->subdevices
[0];
517 s
->type
= COMEDI_SUBD_COUNTER
;
518 s
->subdev_flags
= SDF_READABLE
| SDF_WRITABLE
| SDF_LSAMPL
;
520 s
->maxdata
= 0x00ffffff;
521 s
->insn_read
= s526_gpct_rinsn
;
522 s
->insn_config
= s526_gpct_insn_config
;
523 s
->insn_write
= s526_gpct_winsn
;
526 * Analog Input subdevice
527 * channels 0 to 7 are the regular differential inputs
528 * channel 8 is "reference 0" (+10V)
529 * channel 9 is "reference 1" (0V)
531 s
= &dev
->subdevices
[1];
532 s
->type
= COMEDI_SUBD_AI
;
533 s
->subdev_flags
= SDF_READABLE
| SDF_DIFF
;
536 s
->range_table
= &range_bipolar10
;
537 s
->len_chanlist
= 16;
538 s
->insn_read
= s526_ai_insn_read
;
540 /* Analog Output subdevice */
541 s
= &dev
->subdevices
[2];
542 s
->type
= COMEDI_SUBD_AO
;
543 s
->subdev_flags
= SDF_WRITABLE
;
546 s
->range_table
= &range_bipolar10
;
547 s
->insn_write
= s526_ao_insn_write
;
549 ret
= comedi_alloc_subdev_readback(s
);
553 /* Digital I/O subdevice */
554 s
= &dev
->subdevices
[3];
555 s
->type
= COMEDI_SUBD_DIO
;
556 s
->subdev_flags
= SDF_READABLE
| SDF_WRITABLE
;
559 s
->range_table
= &range_digital
;
560 s
->insn_bits
= s526_dio_insn_bits
;
561 s
->insn_config
= s526_dio_insn_config
;
566 static struct comedi_driver s526_driver
= {
567 .driver_name
= "s526",
568 .module
= THIS_MODULE
,
569 .attach
= s526_attach
,
570 .detach
= comedi_legacy_detach
,
572 module_comedi_driver(s526_driver
);
574 MODULE_AUTHOR("Comedi http://www.comedi.org");
575 MODULE_DESCRIPTION("Comedi low-level driver");
576 MODULE_LICENSE("GPL");