1/*
2 * pcl711.c
3 * Comedi driver for PC-LabCard PCL-711 and AdSys ACL-8112 and compatibles
4 * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
5 *		      Janne Jalkanen <jalkanen@cs.hut.fi>
6 *		      Eric Bunn <ebu@cs.hut.fi>
7 *
8 * COMEDI - Linux Control and Measurement Device Interface
9 * Copyright (C) 1998 David A. Schleef <ds@schleef.org>
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 * GNU General Public License for more details.
20 */
21
22/*
23 * Driver: pcl711
24 * Description: Advantech PCL-711 and 711b, ADLink ACL-8112
25 * Devices: (Advantech) PCL-711 [pcl711]
26 *	    (Advantech) PCL-711B [pcl711b]
27 *	    (AdLink) ACL-8112HG [acl8112hg]
28 *	    (AdLink) ACL-8112DG [acl8112dg]
29 * Author: David A. Schleef <ds@schleef.org>
30 *	   Janne Jalkanen <jalkanen@cs.hut.fi>
31 *	   Eric Bunn <ebu@cs.hut.fi>
32 * Updated:
33 * Status: mostly complete
34 *
35 * Configuration Options:
36 *   [0] - I/O port base
37 *   [1] - IRQ, optional
38 */
39
40#include <linux/module.h>
41#include <linux/delay.h>
42#include <linux/interrupt.h>
43
44#include "../comedidev.h"
45
46#include "comedi_fc.h"
47#include "8253.h"
48
49/*
50 * I/O port register map
51 */
52#define PCL711_TIMER_BASE	0x00
53#define PCL711_AI_LSB_REG	0x04
54#define PCL711_AI_MSB_REG	0x05
55#define PCL711_AI_MSB_DRDY	(1 << 4)
56#define PCL711_AO_LSB_REG(x)	(0x04 + ((x) * 2))
57#define PCL711_AO_MSB_REG(x)	(0x05 + ((x) * 2))
58#define PCL711_DI_LSB_REG	0x06
59#define PCL711_DI_MSB_REG	0x07
60#define PCL711_INT_STAT_REG	0x08
61#define PCL711_INT_STAT_CLR	(0 << 0)  /* any value will work */
62#define PCL711_AI_GAIN_REG	0x09
63#define PCL711_AI_GAIN(x)	(((x) & 0xf) << 0)
64#define PCL711_MUX_REG		0x0a
65#define PCL711_MUX_CHAN(x)	(((x) & 0xf) << 0)
66#define PCL711_MUX_CS0		(1 << 4)
67#define PCL711_MUX_CS1		(1 << 5)
68#define PCL711_MUX_DIFF		(PCL711_MUX_CS0 | PCL711_MUX_CS1)
69#define PCL711_MODE_REG		0x0b
70#define PCL711_MODE_DEFAULT	(0 << 0)
71#define PCL711_MODE_SOFTTRIG	(1 << 0)
72#define PCL711_MODE_EXT		(2 << 0)
73#define PCL711_MODE_EXT_IRQ	(3 << 0)
74#define PCL711_MODE_PACER	(4 << 0)
75#define PCL711_MODE_PACER_IRQ	(6 << 0)
76#define PCL711_MODE_IRQ(x)	(((x) & 0x7) << 4)
77#define PCL711_SOFTTRIG_REG	0x0c
78#define PCL711_SOFTTRIG		(0 << 0)  /* any value will work */
79#define PCL711_DO_LSB_REG	0x0d
80#define PCL711_DO_MSB_REG	0x0e
81
82static const struct comedi_lrange range_pcl711b_ai = {
83	5, {
84		BIP_RANGE(5),
85		BIP_RANGE(2.5),
86		BIP_RANGE(1.25),
87		BIP_RANGE(0.625),
88		BIP_RANGE(0.3125)
89	}
90};
91
92static const struct comedi_lrange range_acl8112hg_ai = {
93	12, {
94		BIP_RANGE(5),
95		BIP_RANGE(0.5),
96		BIP_RANGE(0.05),
97		BIP_RANGE(0.005),
98		UNI_RANGE(10),
99		UNI_RANGE(1),
100		UNI_RANGE(0.1),
101		UNI_RANGE(0.01),
102		BIP_RANGE(10),
103		BIP_RANGE(1),
104		BIP_RANGE(0.1),
105		BIP_RANGE(0.01)
106	}
107};
108
109static const struct comedi_lrange range_acl8112dg_ai = {
110	9, {
111		BIP_RANGE(5),
112		BIP_RANGE(2.5),
113		BIP_RANGE(1.25),
114		BIP_RANGE(0.625),
115		UNI_RANGE(10),
116		UNI_RANGE(5),
117		UNI_RANGE(2.5),
118		UNI_RANGE(1.25),
119		BIP_RANGE(10)
120	}
121};
122
123struct pcl711_board {
124	const char *name;
125	int n_aichan;
126	int n_aochan;
127	int maxirq;
128	const struct comedi_lrange *ai_range_type;
129};
130
131static const struct pcl711_board boardtypes[] = {
132	{
133		.name		= "pcl711",
134		.n_aichan	= 8,
135		.n_aochan	= 1,
136		.ai_range_type	= &range_bipolar5,
137	}, {
138		.name		= "pcl711b",
139		.n_aichan	= 8,
140		.n_aochan	= 1,
141		.maxirq		= 7,
142		.ai_range_type	= &range_pcl711b_ai,
143	}, {
144		.name		= "acl8112hg",
145		.n_aichan	= 16,
146		.n_aochan	= 2,
147		.maxirq		= 15,
148		.ai_range_type	= &range_acl8112hg_ai,
149	}, {
150		.name		= "acl8112dg",
151		.n_aichan	= 16,
152		.n_aochan	= 2,
153		.maxirq		= 15,
154		.ai_range_type	= &range_acl8112dg_ai,
155	},
156};
157
158struct pcl711_private {
159	unsigned int ntrig;
160	unsigned int divisor1;
161	unsigned int divisor2;
162};
163
164static void pcl711_ai_set_mode(struct comedi_device *dev, unsigned int mode)
165{
166	/*
167	 * The pcl711b board uses bits in the mode register to select the
168	 * interrupt. The other boards supported by this driver all use
169	 * jumpers on the board.
170	 *
171	 * Enables the interrupt when needed on the pcl711b board. These
172	 * bits do nothing on the other boards.
173	 */
174	if (mode == PCL711_MODE_EXT_IRQ || mode == PCL711_MODE_PACER_IRQ)
175		mode |= PCL711_MODE_IRQ(dev->irq);
176
177	outb(mode, dev->iobase + PCL711_MODE_REG);
178}
179
180static unsigned int pcl711_ai_get_sample(struct comedi_device *dev,
181					 struct comedi_subdevice *s)
182{
183	unsigned int val;
184
185	val = inb(dev->iobase + PCL711_AI_MSB_REG) << 8;
186	val |= inb(dev->iobase + PCL711_AI_LSB_REG);
187
188	return val & s->maxdata;
189}
190
191static int pcl711_ai_cancel(struct comedi_device *dev,
192			    struct comedi_subdevice *s)
193{
194	outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
195	pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
196	return 0;
197}
198
199static irqreturn_t pcl711_interrupt(int irq, void *d)
200{
201	struct comedi_device *dev = d;
202	struct pcl711_private *devpriv = dev->private;
203	struct comedi_subdevice *s = dev->read_subdev;
204	struct comedi_cmd *cmd = &s->async->cmd;
205	unsigned int data;
206
207	if (!dev->attached) {
208		dev_err(dev->class_dev, "spurious interrupt\n");
209		return IRQ_HANDLED;
210	}
211
212	data = pcl711_ai_get_sample(dev, s);
213
214	outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
215
216	if (comedi_buf_put(s, data) == 0) {
217		s->async->events |= COMEDI_CB_OVERFLOW | COMEDI_CB_ERROR;
218	} else {
219		s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
220		if (cmd->stop_src == TRIG_COUNT && !(--devpriv->ntrig)) {
221			pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
222			s->async->events |= COMEDI_CB_EOA;
223		}
224	}
225	comedi_event(dev, s);
226	return IRQ_HANDLED;
227}
228
229static void pcl711_set_changain(struct comedi_device *dev,
230				struct comedi_subdevice *s,
231				unsigned int chanspec)
232{
233	unsigned int chan = CR_CHAN(chanspec);
234	unsigned int range = CR_RANGE(chanspec);
235	unsigned int aref = CR_AREF(chanspec);
236	unsigned int mux = 0;
237
238	outb(PCL711_AI_GAIN(range), dev->iobase + PCL711_AI_GAIN_REG);
239
240	if (s->n_chan > 8) {
241		/* Select the correct MPC508A chip */
242		if (aref == AREF_DIFF) {
243			chan &= 0x7;
244			mux |= PCL711_MUX_DIFF;
245		} else {
246			if (chan < 8)
247				mux |= PCL711_MUX_CS0;
248			else
249				mux |= PCL711_MUX_CS1;
250		}
251	}
252	outb(mux | PCL711_MUX_CHAN(chan), dev->iobase + PCL711_MUX_REG);
253}
254
255static int pcl711_ai_eoc(struct comedi_device *dev,
256			 struct comedi_subdevice *s,
257			 struct comedi_insn *insn,
258			 unsigned long context)
259{
260	unsigned int status;
261
262	status = inb(dev->iobase + PCL711_AI_MSB_REG);
263	if ((status & PCL711_AI_MSB_DRDY) == 0)
264		return 0;
265	return -EBUSY;
266}
267
268static int pcl711_ai_insn_read(struct comedi_device *dev,
269			       struct comedi_subdevice *s,
270			       struct comedi_insn *insn,
271			       unsigned int *data)
272{
273	int ret;
274	int i;
275
276	pcl711_set_changain(dev, s, insn->chanspec);
277
278	pcl711_ai_set_mode(dev, PCL711_MODE_SOFTTRIG);
279
280	for (i = 0; i < insn->n; i++) {
281		outb(PCL711_SOFTTRIG, dev->iobase + PCL711_SOFTTRIG_REG);
282
283		ret = comedi_timeout(dev, s, insn, pcl711_ai_eoc, 0);
284		if (ret)
285			return ret;
286
287		data[i] = pcl711_ai_get_sample(dev, s);
288	}
289
290	return insn->n;
291}
292
293static int pcl711_ai_cmdtest(struct comedi_device *dev,
294			     struct comedi_subdevice *s, struct comedi_cmd *cmd)
295{
296	struct pcl711_private *devpriv = dev->private;
297	int err = 0;
298	unsigned int arg;
299
300	/* Step 1 : check if triggers are trivially valid */
301
302	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
303	err |= cfc_check_trigger_src(&cmd->scan_begin_src,
304					TRIG_TIMER | TRIG_EXT);
305	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_NOW);
306	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
307	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT | TRIG_NONE);
308
309	if (err)
310		return 1;
311
312	/* Step 2a : make sure trigger sources are unique */
313
314	err |= cfc_check_trigger_is_unique(cmd->scan_begin_src);
315	err |= cfc_check_trigger_is_unique(cmd->stop_src);
316
317	/* Step 2b : and mutually compatible */
318
319	if (err)
320		return 2;
321
322	/* Step 3: check if arguments are trivially valid */
323
324	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
325
326	if (cmd->scan_begin_src == TRIG_EXT) {
327		err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
328	} else {
329#define MAX_SPEED 1000
330		err |= cfc_check_trigger_arg_min(&cmd->scan_begin_arg,
331						 MAX_SPEED);
332	}
333
334	err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
335	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
336
337	if (cmd->stop_src == TRIG_COUNT)
338		err |= cfc_check_trigger_arg_min(&cmd->stop_arg, 1);
339	else	/* TRIG_NONE */
340		err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
341
342	if (err)
343		return 3;
344
345	/* step 4 */
346
347	if (cmd->scan_begin_src == TRIG_TIMER) {
348		arg = cmd->scan_begin_arg;
349		i8253_cascade_ns_to_timer(I8254_OSC_BASE_2MHZ,
350					  &devpriv->divisor1,
351					  &devpriv->divisor2,
352					  &arg, cmd->flags);
353		err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, arg);
354	}
355
356	if (err)
357		return 4;
358
359	return 0;
360}
361
362static void pcl711_ai_load_counters(struct comedi_device *dev)
363{
364	struct pcl711_private *devpriv = dev->private;
365	unsigned long timer_base = dev->iobase + PCL711_TIMER_BASE;
366
367	i8254_set_mode(timer_base, 0, 1, I8254_MODE2 | I8254_BINARY);
368	i8254_set_mode(timer_base, 0, 2, I8254_MODE2 | I8254_BINARY);
369
370	i8254_write(timer_base, 0, 1, devpriv->divisor1);
371	i8254_write(timer_base, 0, 2, devpriv->divisor2);
372}
373
374static int pcl711_ai_cmd(struct comedi_device *dev, struct comedi_subdevice *s)
375{
376	struct pcl711_private *devpriv = dev->private;
377	struct comedi_cmd *cmd = &s->async->cmd;
378
379	pcl711_set_changain(dev, s, cmd->chanlist[0]);
380
381	if (cmd->stop_src == TRIG_COUNT)
382		devpriv->ntrig = cmd->stop_arg;
383
384	if (cmd->scan_begin_src == TRIG_TIMER) {
385		pcl711_ai_load_counters(dev);
386		outb(PCL711_INT_STAT_CLR, dev->iobase + PCL711_INT_STAT_REG);
387		pcl711_ai_set_mode(dev, PCL711_MODE_PACER_IRQ);
388	} else {
389		pcl711_ai_set_mode(dev, PCL711_MODE_EXT_IRQ);
390	}
391
392	return 0;
393}
394
395static void pcl711_ao_write(struct comedi_device *dev,
396			    unsigned int chan, unsigned int val)
397{
398	outb(val & 0xff, dev->iobase + PCL711_AO_LSB_REG(chan));
399	outb((val >> 8) & 0xff, dev->iobase + PCL711_AO_MSB_REG(chan));
400}
401
402static int pcl711_ao_insn_write(struct comedi_device *dev,
403				struct comedi_subdevice *s,
404				struct comedi_insn *insn,
405				unsigned int *data)
406{
407	unsigned int chan = CR_CHAN(insn->chanspec);
408	unsigned int val = s->readback[chan];
409	int i;
410
411	for (i = 0; i < insn->n; i++) {
412		val = data[i];
413		pcl711_ao_write(dev, chan, val);
414	}
415	s->readback[chan] = val;
416
417	return insn->n;
418}
419
420static int pcl711_di_insn_bits(struct comedi_device *dev,
421			       struct comedi_subdevice *s,
422			       struct comedi_insn *insn,
423			       unsigned int *data)
424{
425	unsigned int val;
426
427	val = inb(dev->iobase + PCL711_DI_LSB_REG);
428	val |= (inb(dev->iobase + PCL711_DI_MSB_REG) << 8);
429
430	data[1] = val;
431
432	return insn->n;
433}
434
435static int pcl711_do_insn_bits(struct comedi_device *dev,
436			       struct comedi_subdevice *s,
437			       struct comedi_insn *insn,
438			       unsigned int *data)
439{
440	unsigned int mask;
441
442	mask = comedi_dio_update_state(s, data);
443	if (mask) {
444		if (mask & 0x00ff)
445			outb(s->state & 0xff, dev->iobase + PCL711_DO_LSB_REG);
446		if (mask & 0xff00)
447			outb((s->state >> 8), dev->iobase + PCL711_DO_MSB_REG);
448	}
449
450	data[1] = s->state;
451
452	return insn->n;
453}
454
455static int pcl711_attach(struct comedi_device *dev, struct comedi_devconfig *it)
456{
457	const struct pcl711_board *board = dev->board_ptr;
458	struct pcl711_private *devpriv;
459	struct comedi_subdevice *s;
460	int ret;
461
462	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
463	if (!devpriv)
464		return -ENOMEM;
465
466	ret = comedi_request_region(dev, it->options[0], 0x10);
467	if (ret)
468		return ret;
469
470	if (it->options[1] && it->options[1] <= board->maxirq) {
471		ret = request_irq(it->options[1], pcl711_interrupt, 0,
472				  dev->board_name, dev);
473		if (ret == 0)
474			dev->irq = it->options[1];
475	}
476
477	ret = comedi_alloc_subdevices(dev, 4);
478	if (ret)
479		return ret;
480
481	/* Analog Input subdevice */
482	s = &dev->subdevices[0];
483	s->type		= COMEDI_SUBD_AI;
484	s->subdev_flags	= SDF_READABLE | SDF_GROUND;
485	if (board->n_aichan > 8)
486		s->subdev_flags	|= SDF_DIFF;
487	s->n_chan	= board->n_aichan;
488	s->maxdata	= 0xfff;
489	s->range_table	= board->ai_range_type;
490	s->insn_read	= pcl711_ai_insn_read;
491	if (dev->irq) {
492		dev->read_subdev = s;
493		s->subdev_flags	|= SDF_CMD_READ;
494		s->len_chanlist	= 1;
495		s->do_cmdtest	= pcl711_ai_cmdtest;
496		s->do_cmd	= pcl711_ai_cmd;
497		s->cancel	= pcl711_ai_cancel;
498	}
499
500	/* Analog Output subdevice */
501	s = &dev->subdevices[1];
502	s->type		= COMEDI_SUBD_AO;
503	s->subdev_flags	= SDF_WRITABLE;
504	s->n_chan	= board->n_aochan;
505	s->maxdata	= 0xfff;
506	s->range_table	= &range_bipolar5;
507	s->insn_write	= pcl711_ao_insn_write;
508	s->insn_read	= comedi_readback_insn_read;
509
510	ret = comedi_alloc_subdev_readback(s);
511	if (ret)
512		return ret;
513
514	/* Digital Input subdevice */
515	s = &dev->subdevices[2];
516	s->type		= COMEDI_SUBD_DI;
517	s->subdev_flags	= SDF_READABLE;
518	s->n_chan	= 16;
519	s->maxdata	= 1;
520	s->range_table	= &range_digital;
521	s->insn_bits	= pcl711_di_insn_bits;
522
523	/* Digital Output subdevice */
524	s = &dev->subdevices[3];
525	s->type		= COMEDI_SUBD_DO;
526	s->subdev_flags	= SDF_WRITABLE;
527	s->n_chan	= 16;
528	s->maxdata	= 1;
529	s->range_table	= &range_digital;
530	s->insn_bits	= pcl711_do_insn_bits;
531
532	/* clear DAC */
533	pcl711_ao_write(dev, 0, 0x0);
534	pcl711_ao_write(dev, 1, 0x0);
535
536	return 0;
537}
538
539static struct comedi_driver pcl711_driver = {
540	.driver_name	= "pcl711",
541	.module		= THIS_MODULE,
542	.attach		= pcl711_attach,
543	.detach		= comedi_legacy_detach,
544	.board_name	= &boardtypes[0].name,
545	.num_names	= ARRAY_SIZE(boardtypes),
546	.offset		= sizeof(struct pcl711_board),
547};
548module_comedi_driver(pcl711_driver);
549
550MODULE_AUTHOR("Comedi http://www.comedi.org");
551MODULE_DESCRIPTION("Comedi driver for PCL-711 compatible boards");
552MODULE_LICENSE("GPL");
553