1 /*
2     comedi/drivers/s526.c
3     Sensoray s526 Comedi driver
4 
5     COMEDI - Linux Control and Measurement Device Interface
6     Copyright (C) 2000 David A. Schleef <ds@schleef.org>
7 
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.
12 
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.
17 */
18 /*
19 Driver: s526
20 Description: Sensoray 526 driver
21 Devices: [Sensoray] 526 (s526)
22 Author: Richie
23 	Everett Wang <everett.wang@everteq.com>
24 Updated: Thu, 14 Sep. 2006
25 Status: experimental
26 
27 Encoder works
28 Analog input works
29 Analog output works
30 PWM output works
31 Commands are not supported yet.
32 
33 Configuration Options:
34 
35 comedi_config /dev/comedi0 s526 0x2C0,0x3
36 
37 */
38 
39 #include <linux/module.h>
40 #include "../comedidev.h"
41 #include <asm/byteorder.h>
42 
43 #define S526_START_AI_CONV	0
44 #define S526_AI_READ		0
45 
46 /* Ports */
47 #define S526_NUM_PORTS 27
48 
49 /* registers */
50 #define REG_TCR 0x00
51 #define REG_WDC 0x02
52 #define REG_DAC 0x04
53 #define REG_ADC 0x06
54 #define REG_ADD 0x08
55 #define REG_DIO 0x0A
56 #define REG_IER 0x0C
57 #define REG_ISR 0x0E
58 #define REG_MSC 0x10
59 #define REG_C0L 0x12
60 #define REG_C0H 0x14
61 #define REG_C0M 0x16
62 #define REG_C0C 0x18
63 #define REG_C1L 0x1A
64 #define REG_C1H 0x1C
65 #define REG_C1M 0x1E
66 #define REG_C1C 0x20
67 #define REG_C2L 0x22
68 #define REG_C2H 0x24
69 #define REG_C2M 0x26
70 #define REG_C2C 0x28
71 #define REG_C3L 0x2A
72 #define REG_C3H 0x2C
73 #define REG_C3M 0x2E
74 #define REG_C3C 0x30
75 #define REG_EED 0x32
76 #define REG_EEC 0x34
77 
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;
103 #else
104 #error Unknown bit field order
105 #endif
106 };
107 
108 union cmReg {
109 	struct counter_mode_register_t reg;
110 	unsigned short value;
111 };
112 
113 struct s526_private {
114 	unsigned int gpct_config[4];
115 	unsigned short ai_config;
116 };
117 
s526_gpct_rinsn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)118 static int s526_gpct_rinsn(struct comedi_device *dev,
119 			   struct comedi_subdevice *s,
120 			   struct comedi_insn *insn,
121 			   unsigned int *data)
122 {
123 	unsigned int chan = CR_CHAN(insn->chanspec);
124 	unsigned long chan_iobase = dev->iobase + chan * 8;
125 	unsigned int lo;
126 	unsigned int hi;
127 	int i;
128 
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;
133 
134 		data[i] = (hi << 16) | lo;
135 	}
136 
137 	return insn->n;
138 }
139 
s526_gpct_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)140 static int s526_gpct_insn_config(struct comedi_device *dev,
141 				 struct comedi_subdevice *s,
142 				 struct comedi_insn *insn,
143 				 unsigned int *data)
144 {
145 	struct s526_private *devpriv = dev->private;
146 	unsigned int chan = CR_CHAN(insn->chanspec);
147 	unsigned long chan_iobase = dev->iobase + chan * 8;
148 	unsigned int val;
149 	union cmReg cmReg;
150 
151 	/*  Check what type of Counter the user requested, data[0] contains */
152 	/*  the Application type */
153 	switch (data[0]) {
154 	case INSN_CONFIG_GPCT_QUADRATURE_ENCODER:
155 		/*
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
160 		 */
161 		devpriv->gpct_config[chan] = data[0];
162 
163 #if 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;
177 
178 		outw(cmReg.value, chan_iobase + REG_C0M);
179 
180 		outw(0x0001, chan_iobase + REG_C0H);
181 		outw(0x3C68, chan_iobase + REG_C0L);
182 
183 		/*  Reset the counter */
184 		outw(0x8000, chan_iobase + REG_C0C);
185 		/*  Load the counter from PR0 */
186 		outw(0x4000, chan_iobase + REG_C0C);
187 
188 		/*  Reset RCAP (fires one-shot) */
189 		outw(0x0008, chan_iobase + REG_C0C);
190 
191 #endif
192 
193 #if 1
194 		/*  Set Counter Mode Register */
195 		cmReg.value = data[1] & 0xffff;
196 		outw(cmReg.value, chan_iobase + REG_C0M);
197 
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);
204 			 */
205 		}
206 #else
207 		/*  0 quadrature, 1 software control */
208 		cmReg.reg.countDirCtrl = 0;
209 
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;
215 		else
216 			cmReg.reg.clockSource = 0;
217 
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) {
223 		}*/
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;
228 
229 		/*  Set Counter Mode Register */
230 		cmReg.value = data[1] & 0xffff;
231 		outw(cmReg.value, chan_iobase + REG_C0M);
232 
233 		/*  Load the pre-load register high word */
234 		val = (data[2] >> 16) & 0xffff;
235 		outw(val, chan_iobase + REG_C0H);
236 
237 		/*  Load the pre-load register low word */
238 		val = data[2] & 0xffff;
239 		outw(val, chan_iobase + REG_C0L);
240 
241 		/*  Write the Counter Control Register */
242 		if (data[3]) {
243 			val = data[3] & 0xffff;
244 			outw(val, chan_iobase + REG_C0C);
245 		}
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);
252 		}
253 #endif
254 		break;
255 
256 	case INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR:
257 		/*
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
263 		 */
264 		devpriv->gpct_config[chan] = data[0];
265 
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);
270 
271 		/*  Load the pre-load register 0 high word */
272 		val = (data[2] >> 16) & 0xffff;
273 		outw(val, chan_iobase + REG_C0H);
274 
275 		/*  Load the pre-load register 0 low word */
276 		val = data[2] & 0xffff;
277 		outw(val, chan_iobase + REG_C0L);
278 
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);
283 
284 		/*  Load the pre-load register 1 high word */
285 		val = (data[3] >> 16) & 0xffff;
286 		outw(val, chan_iobase + REG_C0H);
287 
288 		/*  Load the pre-load register 1 low word */
289 		val = data[3] & 0xffff;
290 		outw(val, chan_iobase + REG_C0L);
291 
292 		/*  Write the Counter Control Register */
293 		if (data[4]) {
294 			val = data[4] & 0xffff;
295 			outw(val, chan_iobase + REG_C0C);
296 		}
297 		break;
298 
299 	case INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR:
300 		/*
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
306 		 */
307 		devpriv->gpct_config[chan] = data[0];
308 
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);
313 
314 		/*  Load the pre-load register 0 high word */
315 		val = (data[2] >> 16) & 0xffff;
316 		outw(val, chan_iobase + REG_C0H);
317 
318 		/*  Load the pre-load register 0 low word */
319 		val = data[2] & 0xffff;
320 		outw(val, chan_iobase + REG_C0L);
321 
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);
326 
327 		/*  Load the pre-load register 1 high word */
328 		val = (data[3] >> 16) & 0xffff;
329 		outw(val, chan_iobase + REG_C0H);
330 
331 		/*  Load the pre-load register 1 low word */
332 		val = data[3] & 0xffff;
333 		outw(val, chan_iobase + REG_C0L);
334 
335 		/*  Write the Counter Control Register */
336 		if (data[4]) {
337 			val = data[4] & 0xffff;
338 			outw(val, chan_iobase + REG_C0C);
339 		}
340 		break;
341 
342 	default:
343 		return -EINVAL;
344 	}
345 
346 	return insn->n;
347 }
348 
s526_gpct_winsn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)349 static int s526_gpct_winsn(struct comedi_device *dev,
350 			   struct comedi_subdevice *s,
351 			   struct comedi_insn *insn,
352 			   unsigned int *data)
353 {
354 	struct s526_private *devpriv = dev->private;
355 	unsigned int chan = CR_CHAN(insn->chanspec);
356 	unsigned long chan_iobase = dev->iobase + chan * 8;
357 
358 	inw(chan_iobase + REG_C0M);	/* Is this read required? */
359 
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
368 		 */
369 		if ((data[1] <= data[0]) || !data[0])
370 			return -EINVAL;
371 
372 		/* Fall thru to write the PULSE_WIDTH */
373 
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);
378 		break;
379 
380 	default:
381 		return -EINVAL;
382 	}
383 
384 	return insn->n;
385 }
386 
387 #define ISR_ADC_DONE 0x4
s526_ai_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)388 static int s526_ai_insn_config(struct comedi_device *dev,
389 			       struct comedi_subdevice *s,
390 			       struct comedi_insn *insn, unsigned int *data)
391 {
392 	struct s526_private *devpriv = dev->private;
393 	int result = -EINVAL;
394 
395 	if (insn->n < 1)
396 		return result;
397 
398 	result = insn->n;
399 
400 	/* data[0] : channels was set in relevant bits.
401 	   data[1] : delay
402 	 */
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. */
406 
407 	/*  Enable ADC interrupt */
408 	outw(ISR_ADC_DONE, dev->iobase + REG_IER);
409 	devpriv->ai_config = (data[0] & 0x3ff) << 5;
410 	if (data[1] > 0)
411 		devpriv->ai_config |= 0x8000;	/* set the delay */
412 
413 	devpriv->ai_config |= 0x0001;		/* ADC start bit */
414 
415 	return result;
416 }
417 
s526_ai_eoc(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned long context)418 static int s526_ai_eoc(struct comedi_device *dev,
419 		       struct comedi_subdevice *s,
420 		       struct comedi_insn *insn,
421 		       unsigned long context)
422 {
423 	unsigned int status;
424 
425 	status = inw(dev->iobase + REG_ISR);
426 	if (status & ISR_ADC_DONE)
427 		return 0;
428 	return -EBUSY;
429 }
430 
s526_ai_rinsn(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)431 static int s526_ai_rinsn(struct comedi_device *dev, struct comedi_subdevice *s,
432 			 struct comedi_insn *insn, unsigned int *data)
433 {
434 	struct s526_private *devpriv = dev->private;
435 	unsigned int chan = CR_CHAN(insn->chanspec);
436 	int n;
437 	unsigned short value;
438 	unsigned int d;
439 	int ret;
440 
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;
445 
446 	/* convert n samples */
447 	for (n = 0; n < insn->n; n++) {
448 		/* trigger conversion */
449 		outw(value, dev->iobase + REG_ADC);
450 
451 		/* wait for conversion to end */
452 		ret = comedi_timeout(dev, s, insn, s526_ai_eoc, 0);
453 		if (ret)
454 			return ret;
455 
456 		outw(ISR_ADC_DONE, dev->iobase + REG_ISR);
457 
458 		/* read data */
459 		d = inw(dev->iobase + REG_ADD);
460 
461 		/* munge data */
462 		data[n] = d ^ 0x8000;
463 	}
464 
465 	/* return the number of samples read/written */
466 	return n;
467 }
468 
s526_ao_insn_write(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)469 static int s526_ao_insn_write(struct comedi_device *dev,
470 			      struct comedi_subdevice *s,
471 			      struct comedi_insn *insn,
472 			      unsigned int *data)
473 {
474 	unsigned int chan = CR_CHAN(insn->chanspec);
475 	unsigned int val = s->readback[chan];
476 	int i;
477 
478 	outw(chan << 1, dev->iobase + REG_DAC);
479 
480 	for (i = 0; i < insn->n; i++) {
481 		val = data[i];
482 		outw(val, dev->iobase + REG_ADD);
483 		/* starts the D/A conversion */
484 		outw((chan << 1) | 1, dev->iobase + REG_DAC);
485 	}
486 	s->readback[chan] = val;
487 
488 	return insn->n;
489 }
490 
s526_dio_insn_bits(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)491 static int s526_dio_insn_bits(struct comedi_device *dev,
492 			      struct comedi_subdevice *s,
493 			      struct comedi_insn *insn,
494 			      unsigned int *data)
495 {
496 	if (comedi_dio_update_state(s, data))
497 		outw(s->state, dev->iobase + REG_DIO);
498 
499 	data[1] = inw(dev->iobase + REG_DIO) & 0xff;
500 
501 	return insn->n;
502 }
503 
s526_dio_insn_config(struct comedi_device * dev,struct comedi_subdevice * s,struct comedi_insn * insn,unsigned int * data)504 static int s526_dio_insn_config(struct comedi_device *dev,
505 				struct comedi_subdevice *s,
506 				struct comedi_insn *insn,
507 				unsigned int *data)
508 {
509 	unsigned int chan = CR_CHAN(insn->chanspec);
510 	unsigned int mask;
511 	int ret;
512 
513 	if (chan < 4)
514 		mask = 0x0f;
515 	else
516 		mask = 0xf0;
517 
518 	ret = comedi_dio_insn_config(dev, s, insn, data, mask);
519 	if (ret)
520 		return ret;
521 
522 	/* bit 10/11 set the group 1/2's mode */
523 	if (s->io_bits & 0x0f)
524 		s->state |= (1 << 10);
525 	else
526 		s->state &= ~(1 << 10);
527 	if (s->io_bits & 0xf0)
528 		s->state |= (1 << 11);
529 	else
530 		s->state &= ~(1 << 11);
531 
532 	outw(s->state, dev->iobase + REG_DIO);
533 
534 	return insn->n;
535 }
536 
s526_attach(struct comedi_device * dev,struct comedi_devconfig * it)537 static int s526_attach(struct comedi_device *dev, struct comedi_devconfig *it)
538 {
539 	struct s526_private *devpriv;
540 	struct comedi_subdevice *s;
541 	int ret;
542 
543 	ret = comedi_request_region(dev, it->options[0], 0x40);
544 	if (ret)
545 		return ret;
546 
547 	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
548 	if (!devpriv)
549 		return -ENOMEM;
550 
551 	ret = comedi_alloc_subdevices(dev, 4);
552 	if (ret)
553 		return ret;
554 
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;
559 	s->n_chan = 4;
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;
564 
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) */
571 	s->n_chan = 10;
572 	s->maxdata = 0xffff;
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;
577 
578 	s = &dev->subdevices[2];
579 	/* analog output subdevice */
580 	s->type = COMEDI_SUBD_AO;
581 	s->subdev_flags = SDF_WRITABLE;
582 	s->n_chan = 4;
583 	s->maxdata = 0xffff;
584 	s->range_table = &range_bipolar10;
585 	s->insn_write = s526_ao_insn_write;
586 
587 	ret = comedi_alloc_subdev_readback(s);
588 	if (ret)
589 		return ret;
590 
591 	s = &dev->subdevices[3];
592 	/* digital i/o subdevice */
593 	s->type = COMEDI_SUBD_DIO;
594 	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
595 	s->n_chan = 8;
596 	s->maxdata = 1;
597 	s->range_table = &range_digital;
598 	s->insn_bits = s526_dio_insn_bits;
599 	s->insn_config = s526_dio_insn_config;
600 
601 	return 0;
602 }
603 
604 static struct comedi_driver s526_driver = {
605 	.driver_name	= "s526",
606 	.module		= THIS_MODULE,
607 	.attach		= s526_attach,
608 	.detach		= comedi_legacy_detach,
609 };
610 module_comedi_driver(s526_driver);
611 
612 MODULE_AUTHOR("Comedi http://www.comedi.org");
613 MODULE_DESCRIPTION("Comedi low-level driver");
614 MODULE_LICENSE("GPL");
615