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/*
19Driver: s526
20Description: Sensoray 526 driver
21Devices: [Sensoray] 526 (s526)
22Author: Richie
23	Everett Wang <everett.wang@everteq.com>
24Updated: Thu, 14 Sep. 2006
25Status: experimental
26
27Encoder works
28Analog input works
29Analog output works
30PWM output works
31Commands are not supported yet.
32
33Configuration Options:
34
35comedi_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
78struct 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
108union cmReg {
109	struct counter_mode_register_t reg;
110	unsigned short value;
111};
112
113struct s526_private {
114	unsigned int gpct_config[4];
115	unsigned short ai_config;
116};
117
118static 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
140static 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
349static 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
388static 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
418static 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
431static 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
469static 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
491static 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
504static 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
537static 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	s->insn_read = comedi_readback_insn_read;
587
588	ret = comedi_alloc_subdev_readback(s);
589	if (ret)
590		return ret;
591
592	s = &dev->subdevices[3];
593	/* digital i/o subdevice */
594	s->type = COMEDI_SUBD_DIO;
595	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
596	s->n_chan = 8;
597	s->maxdata = 1;
598	s->range_table = &range_digital;
599	s->insn_bits = s526_dio_insn_bits;
600	s->insn_config = s526_dio_insn_config;
601
602	return 0;
603}
604
605static struct comedi_driver s526_driver = {
606	.driver_name	= "s526",
607	.module		= THIS_MODULE,
608	.attach		= s526_attach,
609	.detach		= comedi_legacy_detach,
610};
611module_comedi_driver(s526_driver);
612
613MODULE_AUTHOR("Comedi http://www.comedi.org");
614MODULE_DESCRIPTION("Comedi low-level driver");
615MODULE_LICENSE("GPL");
616