From fc59905e44d43fb2121c87b2c4381e3d0efd87b6 Mon Sep 17 00:00:00 2001
From: David Schleef <ds@schleef.org>
Date: Thu, 12 Feb 2009 15:30:25 -0800
Subject: [PATCH] Staging: comedi: add rti800 driver

for Analog Devices RTI-800/815 devices

From: David Schleef <ds@schleef.org>
Cc: Frank Mori Hess <fmhess@users.sourceforge.net>
Cc: Ian Abbott <abbotti@mev.co.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
---
 drivers/staging/comedi/drivers/am9513.h |  79 ++++
 drivers/staging/comedi/drivers/rti800.c | 456 ++++++++++++++++++++++++
 2 files changed, 535 insertions(+)
 create mode 100644 drivers/staging/comedi/drivers/am9513.h
 create mode 100644 drivers/staging/comedi/drivers/rti800.c

diff --git a/drivers/staging/comedi/drivers/am9513.h b/drivers/staging/comedi/drivers/am9513.h
new file mode 100644
index 0000000000000..f533cf1658cf7
--- /dev/null
+++ b/drivers/staging/comedi/drivers/am9513.h
@@ -0,0 +1,79 @@
+/*
+    module/am9513.h
+    value added preprocessor definitions for Am9513 timer chip
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+*/
+
+#ifndef _AM9513_H_
+#define _AM9513_H_
+
+#if 0
+
+/*
+ *	Before including this file, the following need to be defined:
+ */
+#define Am9513_8BITBUS xxx
+/* or */
+#define Am9513_16BITBUS xxx
+
+#define Am9513_output_control(a)	xxx
+#define Am9513_input_status()		xxx
+#define Am9513_output_data(a)		xxx
+#define Am9513_input_data()		xxx
+
+#endif
+
+/*
+ *
+ */
+
+#ifdef Am9513_8BITBUS
+
+#define Am9513_write_register(reg,val)				\
+	do{							\
+		Am9513_output_control(reg);			\
+		Am9513_output_data(val>>8);			\
+		Am9513_output_data(val&0xff);			\
+	}while(0)
+
+#define Am9513_read_register(reg,val)				\
+	do{							\
+		Am9513_output_control(reg);			\
+		val=Am9513_input_data()<<8;			\
+		val|=Am9513_input_data();			\
+	}while(0)
+
+#else /* Am9513_16BITBUS */
+
+#define Am9513_write_register(reg,val)				\
+	do{							\
+		Am9513_output_control(reg);			\
+		Am9513_output_data(val);			\
+	}while(0)
+
+#define Am9513_read_register(reg,val)				\
+	do{							\
+		Am9513_output_control(reg);			\
+		val=Am9513_input_data();			\
+	}while(0)
+
+#endif
+
+#endif
diff --git a/drivers/staging/comedi/drivers/rti800.c b/drivers/staging/comedi/drivers/rti800.c
new file mode 100644
index 0000000000000..35250b9ae4482
--- /dev/null
+++ b/drivers/staging/comedi/drivers/rti800.c
@@ -0,0 +1,456 @@
+/*
+   comedi/drivers/rti800.c
+   Hardware driver for Analog Devices RTI-800/815 board
+
+   COMEDI - Linux Control and Measurement Device Interface
+   Copyright (C) 1998 David A. Schleef <ds@schleef.org>
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+ */
+/*
+Driver: rti800
+Description: Analog Devices RTI-800/815
+Author: ds
+Status: unknown
+Updated: Fri, 05 Sep 2008 14:50:44 +0100
+Devices: [Analog Devices] RTI-800 (rti800), RTI-815 (rti815)
+
+Configuration options:
+  [0] - I/O port base address
+  [1] - IRQ
+  [2] - A/D reference
+          0 = differential
+          1 = pseudodifferential (common)
+          2 = single-ended
+  [3] - A/D range
+          0 = [-10,10]
+          1 = [-5,5]
+          2 = [0,10]
+  [4] - A/D encoding
+          0 = two's complement
+          1 = straight binary
+  [5] - DAC 0 range
+          0 = [-10,10]
+          1 = [0,10]
+  [6] - DAC 0 encoding
+          0 = two's complement
+          1 = straight binary
+  [7] - DAC 1 range (same as DAC 0)
+  [8] - DAC 1 encoding (same as DAC 0)
+*/
+
+#include "../comedidev.h"
+
+#include <linux/ioport.h>
+
+#define RTI800_SIZE 16
+
+#define RTI800_CSR 0
+#define RTI800_MUXGAIN 1
+#define RTI800_CONVERT 2
+#define RTI800_ADCLO 3
+#define RTI800_ADCHI 4
+#define RTI800_DAC0LO 5
+#define RTI800_DAC0HI 6
+#define RTI800_DAC1LO 7
+#define RTI800_DAC1HI 8
+#define RTI800_CLRFLAGS 9
+#define RTI800_DI 10
+#define RTI800_DO 11
+#define RTI800_9513A_DATA 12
+#define RTI800_9513A_CNTRL 13
+#define RTI800_9513A_STATUS 13
+
+/*
+ * flags for CSR register
+ */
+
+#define RTI800_BUSY		0x80
+#define RTI800_DONE		0x40
+#define RTI800_OVERRUN		0x20
+#define RTI800_TCR		0x10
+#define RTI800_DMA_ENAB		0x08
+#define RTI800_INTR_TC		0x04
+#define RTI800_INTR_EC		0x02
+#define RTI800_INTR_OVRN	0x01
+
+#define Am9513_8BITBUS
+
+#define Am9513_output_control(a)	outb(a,dev->iobase+RTI800_9513A_CNTRL)
+#define Am9513_output_data(a)		outb(a,dev->iobase+RTI800_9513A_DATA)
+#define Am9513_input_data()		inb(dev->iobase+RTI800_9513A_DATA)
+#define Am9513_input_status()		inb(dev->iobase+RTI800_9513A_STATUS)
+
+#include "am9513.h"
+
+static const comedi_lrange range_rti800_ai_10_bipolar = { 4, {
+			BIP_RANGE(10),
+			BIP_RANGE(1),
+			BIP_RANGE(0.1),
+			BIP_RANGE(0.02)
+	}
+};
+static const comedi_lrange range_rti800_ai_5_bipolar = { 4, {
+			BIP_RANGE(5),
+			BIP_RANGE(0.5),
+			BIP_RANGE(0.05),
+			BIP_RANGE(0.01)
+	}
+};
+static const comedi_lrange range_rti800_ai_unipolar = { 4, {
+			UNI_RANGE(10),
+			UNI_RANGE(1),
+			UNI_RANGE(0.1),
+			UNI_RANGE(0.02)
+	}
+};
+
+typedef struct {
+	const char *name;
+	int has_ao;
+} boardtype;
+static const boardtype boardtypes[] = {
+	{"rti800", 0},
+	{"rti815", 1},
+};
+
+#define this_board ((const boardtype *)dev->board_ptr)
+
+static int rti800_attach(comedi_device * dev, comedi_devconfig * it);
+static int rti800_detach(comedi_device * dev);
+static comedi_driver driver_rti800 = {
+      driver_name:"rti800",
+      module:THIS_MODULE,
+      attach:rti800_attach,
+      detach:rti800_detach,
+      num_names:sizeof(boardtypes) / sizeof(boardtype),
+      board_name:&boardtypes[0].name,
+      offset:sizeof(boardtype),
+};
+
+COMEDI_INITCLEANUP(driver_rti800);
+
+static irqreturn_t rti800_interrupt(int irq, void *dev PT_REGS_ARG);
+
+typedef struct {
+	enum {
+		adc_diff, adc_pseudodiff, adc_singleended
+	} adc_mux;
+	enum {
+		adc_bipolar10, adc_bipolar5, adc_unipolar10
+	} adc_range;
+	enum {
+		adc_2comp, adc_straight
+	} adc_coding;
+	enum {
+		dac_bipolar10, dac_unipolar10
+	} dac0_range, dac1_range;
+	enum {
+		dac_2comp, dac_straight
+	} dac0_coding, dac1_coding;
+	const comedi_lrange *ao_range_type_list[2];
+	lsampl_t ao_readback[2];
+	int muxgain_bits;
+} rti800_private;
+
+#define devpriv ((rti800_private *)dev->private)
+
+#define RTI800_TIMEOUT 100
+
+static irqreturn_t rti800_interrupt(int irq, void *dev PT_REGS_ARG)
+{
+	return IRQ_HANDLED;
+}
+
+// settling delay times in usec for different gains
+static const int gaindelay[] = { 10, 20, 40, 80 };
+
+static int rti800_ai_insn_read(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int i, t;
+	int status;
+	int chan = CR_CHAN(insn->chanspec);
+	unsigned gain = CR_RANGE(insn->chanspec);
+	unsigned muxgain_bits;
+
+	inb(dev->iobase + RTI800_ADCHI);
+	outb(0, dev->iobase + RTI800_CLRFLAGS);
+
+	muxgain_bits = chan | (gain << 5);
+	if (muxgain_bits != devpriv->muxgain_bits) {
+		devpriv->muxgain_bits = muxgain_bits;
+		outb(devpriv->muxgain_bits, dev->iobase + RTI800_MUXGAIN);
+		/* without a delay here, the RTI_OVERRUN bit
+		 * gets set, and you will have an error. */
+		if (insn->n > 0) {
+			BUG_ON(gain >=
+				sizeof(gaindelay) / sizeof(gaindelay[0]));
+			comedi_udelay(gaindelay[gain]);
+		}
+	}
+
+	for (i = 0; i < insn->n; i++) {
+		outb(0, dev->iobase + RTI800_CONVERT);
+		for (t = RTI800_TIMEOUT; t; t--) {
+			status = inb(dev->iobase + RTI800_CSR);
+			if (status & RTI800_OVERRUN) {
+				rt_printk("rti800: a/d overrun\n");
+				outb(0, dev->iobase + RTI800_CLRFLAGS);
+				return -EIO;
+			}
+			if (status & RTI800_DONE)
+				break;
+			comedi_udelay(1);
+		}
+		if (t == 0) {
+			rt_printk("rti800: timeout\n");
+			return -ETIME;
+		}
+		data[i] = inb(dev->iobase + RTI800_ADCLO);
+		data[i] |= (0xf & inb(dev->iobase + RTI800_ADCHI)) << 8;
+
+		if (devpriv->adc_coding == adc_2comp) {
+			data[i] ^= 0x800;
+		}
+	}
+
+	return i;
+}
+
+static int rti800_ao_insn_read(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int i;
+	int chan = CR_CHAN(insn->chanspec);
+
+	for (i = 0; i < insn->n; i++)
+		data[i] = devpriv->ao_readback[chan];
+
+	return i;
+}
+
+static int rti800_ao_insn_write(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	int chan = CR_CHAN(insn->chanspec);
+	int d;
+	int i;
+
+	for (i = 0; i < insn->n; i++) {
+		devpriv->ao_readback[chan] = d = data[i];
+		if (devpriv->dac0_coding == dac_2comp) {
+			d ^= 0x800;
+		}
+		outb(d & 0xff,
+			dev->iobase + (chan ? RTI800_DAC1LO : RTI800_DAC0LO));
+		outb(d >> 8,
+			dev->iobase + (chan ? RTI800_DAC1HI : RTI800_DAC0HI));
+	}
+	return i;
+}
+
+static int rti800_di_insn_bits(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	if (insn->n != 2)
+		return -EINVAL;
+	data[1] = inb(dev->iobase + RTI800_DI);
+	return 2;
+}
+
+static int rti800_do_insn_bits(comedi_device * dev, comedi_subdevice * s,
+	comedi_insn * insn, lsampl_t * data)
+{
+	if (insn->n != 2)
+		return -EINVAL;
+
+	if (data[0]) {
+		s->state &= ~data[0];
+		s->state |= data[0] & data[1];
+		/* Outputs are inverted... */
+		outb(s->state ^ 0xff, dev->iobase + RTI800_DO);
+	}
+
+	data[1] = s->state;
+
+	return 2;
+}
+
+/*
+   options[0] - I/O port
+   options[1] - irq
+   options[2] - a/d mux
+   	0=differential, 1=pseudodiff, 2=single
+   options[3] - a/d range
+   	0=bipolar10, 1=bipolar5, 2=unipolar10
+   options[4] - a/d coding
+   	0=2's comp, 1=straight binary
+   options[5] - dac0 range
+   	0=bipolar10, 1=unipolar10
+   options[6] - dac0 coding
+   	0=2's comp, 1=straight binary
+   options[7] - dac1 range
+   options[8] - dac1 coding
+ */
+
+static int rti800_attach(comedi_device * dev, comedi_devconfig * it)
+{
+	unsigned int irq;
+	unsigned long iobase;
+	int ret;
+	comedi_subdevice *s;
+
+	iobase = it->options[0];
+	printk("comedi%d: rti800: 0x%04lx ", dev->minor, iobase);
+	if (!request_region(iobase, RTI800_SIZE, "rti800")) {
+		printk("I/O port conflict\n");
+		return -EIO;
+	}
+	dev->iobase = iobase;
+
+#ifdef DEBUG
+	printk("fingerprint=%x,%x,%x,%x,%x ",
+		inb(dev->iobase + 0),
+		inb(dev->iobase + 1),
+		inb(dev->iobase + 2),
+		inb(dev->iobase + 3), inb(dev->iobase + 4));
+#endif
+
+	outb(0, dev->iobase + RTI800_CSR);
+	inb(dev->iobase + RTI800_ADCHI);
+	outb(0, dev->iobase + RTI800_CLRFLAGS);
+
+	irq = it->options[1];
+	if (irq) {
+		printk("( irq = %u )", irq);
+		if ((ret = comedi_request_irq(irq, rti800_interrupt, 0,
+					"rti800", dev)) < 0) {
+			printk(" Failed to allocate IRQ\n");
+			return ret;
+		}
+		dev->irq = irq;
+	} else {
+		printk("( no irq )");
+	}
+
+	dev->board_name = this_board->name;
+
+	if ((ret = alloc_subdevices(dev, 4)) < 0)
+		return ret;
+	if ((ret = alloc_private(dev, sizeof(rti800_private))) < 0)
+		return ret;
+
+	devpriv->adc_mux = it->options[2];
+	devpriv->adc_range = it->options[3];
+	devpriv->adc_coding = it->options[4];
+	devpriv->dac0_range = it->options[5];
+	devpriv->dac0_coding = it->options[6];
+	devpriv->dac1_range = it->options[7];
+	devpriv->dac1_coding = it->options[8];
+	devpriv->muxgain_bits = -1;
+
+	s = dev->subdevices + 0;
+	/* ai subdevice */
+	s->type = COMEDI_SUBD_AI;
+	s->subdev_flags = SDF_READABLE | SDF_GROUND;
+	s->n_chan = (devpriv->adc_mux ? 16 : 8);
+	s->insn_read = rti800_ai_insn_read;
+	s->maxdata = 0xfff;
+	switch (devpriv->adc_range) {
+	case adc_bipolar10:
+		s->range_table = &range_rti800_ai_10_bipolar;
+		break;
+	case adc_bipolar5:
+		s->range_table = &range_rti800_ai_5_bipolar;
+		break;
+	case adc_unipolar10:
+		s->range_table = &range_rti800_ai_unipolar;
+		break;
+	}
+
+	s++;
+	if (this_board->has_ao) {
+		/* ao subdevice (only on rti815) */
+		s->type = COMEDI_SUBD_AO;
+		s->subdev_flags = SDF_WRITABLE;
+		s->n_chan = 2;
+		s->insn_read = rti800_ao_insn_read;
+		s->insn_write = rti800_ao_insn_write;
+		s->maxdata = 0xfff;
+		s->range_table_list = devpriv->ao_range_type_list;
+		switch (devpriv->dac0_range) {
+		case dac_bipolar10:
+			devpriv->ao_range_type_list[0] = &range_bipolar10;
+			break;
+		case dac_unipolar10:
+			devpriv->ao_range_type_list[0] = &range_unipolar10;
+			break;
+		}
+		switch (devpriv->dac1_range) {
+		case dac_bipolar10:
+			devpriv->ao_range_type_list[1] = &range_bipolar10;
+			break;
+		case dac_unipolar10:
+			devpriv->ao_range_type_list[1] = &range_unipolar10;
+			break;
+		}
+	} else {
+		s->type = COMEDI_SUBD_UNUSED;
+	}
+
+	s++;
+	/* di */
+	s->type = COMEDI_SUBD_DI;
+	s->subdev_flags = SDF_READABLE;
+	s->n_chan = 8;
+	s->insn_bits = rti800_di_insn_bits;
+	s->maxdata = 1;
+	s->range_table = &range_digital;
+
+	s++;
+	/* do */
+	s->type = COMEDI_SUBD_DO;
+	s->subdev_flags = SDF_WRITABLE;
+	s->n_chan = 8;
+	s->insn_bits = rti800_do_insn_bits;
+	s->maxdata = 1;
+	s->range_table = &range_digital;
+
+/* don't yet know how to deal with counter/timers */
+#if 0
+	s++;
+	/* do */
+	s->type = COMEDI_SUBD_TIMER;
+#endif
+
+	printk("\n");
+
+	return 0;
+}
+
+static int rti800_detach(comedi_device * dev)
+{
+	printk("comedi%d: rti800: remove\n", dev->minor);
+
+	if (dev->iobase)
+		release_region(dev->iobase, RTI800_SIZE);
+
+	if (dev->irq)
+		comedi_free_irq(dev->irq, dev);
+
+	return 0;
+}
-- 
2.30.2