162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * comedi/drivers/dt2815.c
462306a36Sopenharmony_ci * Hardware driver for Data Translation DT2815
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * COMEDI - Linux Control and Measurement Device Interface
762306a36Sopenharmony_ci * Copyright (C) 1999 Anders Blomdell <anders.blomdell@control.lth.se>
862306a36Sopenharmony_ci */
962306a36Sopenharmony_ci/*
1062306a36Sopenharmony_ci * Driver: dt2815
1162306a36Sopenharmony_ci * Description: Data Translation DT2815
1262306a36Sopenharmony_ci * Author: ds
1362306a36Sopenharmony_ci * Status: mostly complete, untested
1462306a36Sopenharmony_ci * Devices: [Data Translation] DT2815 (dt2815)
1562306a36Sopenharmony_ci *
1662306a36Sopenharmony_ci * I'm not sure anyone has ever tested this board.  If you have information
1762306a36Sopenharmony_ci * contrary, please update.
1862306a36Sopenharmony_ci *
1962306a36Sopenharmony_ci * Configuration options:
2062306a36Sopenharmony_ci * [0] - I/O port base base address
2162306a36Sopenharmony_ci * [1] - IRQ (unused)
2262306a36Sopenharmony_ci * [2] - Voltage unipolar/bipolar configuration
2362306a36Sopenharmony_ci *	0 == unipolar 5V  (0V -- +5V)
2462306a36Sopenharmony_ci *	1 == bipolar 5V  (-5V -- +5V)
2562306a36Sopenharmony_ci * [3] - Current offset configuration
2662306a36Sopenharmony_ci *	0 == disabled  (0mA -- +32mAV)
2762306a36Sopenharmony_ci *	1 == enabled  (+4mA -- +20mAV)
2862306a36Sopenharmony_ci * [4] - Firmware program configuration
2962306a36Sopenharmony_ci *	0 == program 1 (see manual table 5-4)
3062306a36Sopenharmony_ci *	1 == program 2 (see manual table 5-4)
3162306a36Sopenharmony_ci *	2 == program 3 (see manual table 5-4)
3262306a36Sopenharmony_ci *	3 == program 4 (see manual table 5-4)
3362306a36Sopenharmony_ci * [5] - Analog output 0 range configuration
3462306a36Sopenharmony_ci *	0 == voltage
3562306a36Sopenharmony_ci *	1 == current
3662306a36Sopenharmony_ci * [6] - Analog output 1 range configuration (same options)
3762306a36Sopenharmony_ci * [7] - Analog output 2 range configuration (same options)
3862306a36Sopenharmony_ci * [8] - Analog output 3 range configuration (same options)
3962306a36Sopenharmony_ci * [9] - Analog output 4 range configuration (same options)
4062306a36Sopenharmony_ci * [10] - Analog output 5 range configuration (same options)
4162306a36Sopenharmony_ci * [11] - Analog output 6 range configuration (same options)
4262306a36Sopenharmony_ci * [12] - Analog output 7 range configuration (same options)
4362306a36Sopenharmony_ci */
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci#include <linux/module.h>
4662306a36Sopenharmony_ci#include <linux/comedi/comedidev.h>
4762306a36Sopenharmony_ci#include <linux/delay.h>
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci#define DT2815_DATA 0
5062306a36Sopenharmony_ci#define DT2815_STATUS 1
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistruct dt2815_private {
5362306a36Sopenharmony_ci	const struct comedi_lrange *range_type_list[8];
5462306a36Sopenharmony_ci	unsigned int ao_readback[8];
5562306a36Sopenharmony_ci};
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int dt2815_ao_status(struct comedi_device *dev,
5862306a36Sopenharmony_ci			    struct comedi_subdevice *s,
5962306a36Sopenharmony_ci			    struct comedi_insn *insn,
6062306a36Sopenharmony_ci			    unsigned long context)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	unsigned int status;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	status = inb(dev->iobase + DT2815_STATUS);
6562306a36Sopenharmony_ci	if (status == context)
6662306a36Sopenharmony_ci		return 0;
6762306a36Sopenharmony_ci	return -EBUSY;
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic int dt2815_ao_insn_read(struct comedi_device *dev,
7162306a36Sopenharmony_ci			       struct comedi_subdevice *s,
7262306a36Sopenharmony_ci			       struct comedi_insn *insn, unsigned int *data)
7362306a36Sopenharmony_ci{
7462306a36Sopenharmony_ci	struct dt2815_private *devpriv = dev->private;
7562306a36Sopenharmony_ci	int i;
7662306a36Sopenharmony_ci	int chan = CR_CHAN(insn->chanspec);
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	for (i = 0; i < insn->n; i++)
7962306a36Sopenharmony_ci		data[i] = devpriv->ao_readback[chan];
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return i;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int dt2815_ao_insn(struct comedi_device *dev, struct comedi_subdevice *s,
8562306a36Sopenharmony_ci			  struct comedi_insn *insn, unsigned int *data)
8662306a36Sopenharmony_ci{
8762306a36Sopenharmony_ci	struct dt2815_private *devpriv = dev->private;
8862306a36Sopenharmony_ci	int i;
8962306a36Sopenharmony_ci	int chan = CR_CHAN(insn->chanspec);
9062306a36Sopenharmony_ci	unsigned int lo, hi;
9162306a36Sopenharmony_ci	int ret;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	for (i = 0; i < insn->n; i++) {
9462306a36Sopenharmony_ci		/* FIXME: lo bit 0 chooses voltage output or current output */
9562306a36Sopenharmony_ci		lo = ((data[i] & 0x0f) << 4) | (chan << 1) | 0x01;
9662306a36Sopenharmony_ci		hi = (data[i] & 0xff0) >> 4;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci		ret = comedi_timeout(dev, s, insn, dt2815_ao_status, 0x00);
9962306a36Sopenharmony_ci		if (ret)
10062306a36Sopenharmony_ci			return ret;
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci		outb(lo, dev->iobase + DT2815_DATA);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci		ret = comedi_timeout(dev, s, insn, dt2815_ao_status, 0x10);
10562306a36Sopenharmony_ci		if (ret)
10662306a36Sopenharmony_ci			return ret;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci		outb(hi, dev->iobase + DT2815_DATA);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		devpriv->ao_readback[chan] = data[i];
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci	return i;
11362306a36Sopenharmony_ci}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci/*
11662306a36Sopenharmony_ci * options[0]   Board base address
11762306a36Sopenharmony_ci * options[1]   IRQ (not applicable)
11862306a36Sopenharmony_ci * options[2]   Voltage unipolar/bipolar configuration
11962306a36Sopenharmony_ci *		0 == unipolar 5V  (0V -- +5V)
12062306a36Sopenharmony_ci *		1 == bipolar 5V  (-5V -- +5V)
12162306a36Sopenharmony_ci * options[3]   Current offset configuration
12262306a36Sopenharmony_ci *		0 == disabled  (0mA -- +32mAV)
12362306a36Sopenharmony_ci *		1 == enabled  (+4mA -- +20mAV)
12462306a36Sopenharmony_ci * options[4]   Firmware program configuration
12562306a36Sopenharmony_ci *		0 == program 1 (see manual table 5-4)
12662306a36Sopenharmony_ci *		1 == program 2 (see manual table 5-4)
12762306a36Sopenharmony_ci *		2 == program 3 (see manual table 5-4)
12862306a36Sopenharmony_ci *		3 == program 4 (see manual table 5-4)
12962306a36Sopenharmony_ci * options[5]   Analog output 0 range configuration
13062306a36Sopenharmony_ci *		0 == voltage
13162306a36Sopenharmony_ci *		1 == current
13262306a36Sopenharmony_ci * options[6]   Analog output 1 range configuration
13362306a36Sopenharmony_ci * ...
13462306a36Sopenharmony_ci * options[12]   Analog output 7 range configuration
13562306a36Sopenharmony_ci *		0 == voltage
13662306a36Sopenharmony_ci *		1 == current
13762306a36Sopenharmony_ci */
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_cistatic int dt2815_attach(struct comedi_device *dev, struct comedi_devconfig *it)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	struct dt2815_private *devpriv;
14262306a36Sopenharmony_ci	struct comedi_subdevice *s;
14362306a36Sopenharmony_ci	int i;
14462306a36Sopenharmony_ci	const struct comedi_lrange *current_range_type, *voltage_range_type;
14562306a36Sopenharmony_ci	int ret;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	ret = comedi_request_region(dev, it->options[0], 0x2);
14862306a36Sopenharmony_ci	if (ret)
14962306a36Sopenharmony_ci		return ret;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	ret = comedi_alloc_subdevices(dev, 1);
15262306a36Sopenharmony_ci	if (ret)
15362306a36Sopenharmony_ci		return ret;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
15662306a36Sopenharmony_ci	if (!devpriv)
15762306a36Sopenharmony_ci		return -ENOMEM;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	s = &dev->subdevices[0];
16062306a36Sopenharmony_ci	/* ao subdevice */
16162306a36Sopenharmony_ci	s->type = COMEDI_SUBD_AO;
16262306a36Sopenharmony_ci	s->subdev_flags = SDF_WRITABLE;
16362306a36Sopenharmony_ci	s->maxdata = 0xfff;
16462306a36Sopenharmony_ci	s->n_chan = 8;
16562306a36Sopenharmony_ci	s->insn_write = dt2815_ao_insn;
16662306a36Sopenharmony_ci	s->insn_read = dt2815_ao_insn_read;
16762306a36Sopenharmony_ci	s->range_table_list = devpriv->range_type_list;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	current_range_type = (it->options[3])
17062306a36Sopenharmony_ci	    ? &range_4_20mA : &range_0_32mA;
17162306a36Sopenharmony_ci	voltage_range_type = (it->options[2])
17262306a36Sopenharmony_ci	    ? &range_bipolar5 : &range_unipolar5;
17362306a36Sopenharmony_ci	for (i = 0; i < 8; i++) {
17462306a36Sopenharmony_ci		devpriv->range_type_list[i] = (it->options[5 + i])
17562306a36Sopenharmony_ci		    ? current_range_type : voltage_range_type;
17662306a36Sopenharmony_ci	}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	/* Init the 2815 */
17962306a36Sopenharmony_ci	outb(0x00, dev->iobase + DT2815_STATUS);
18062306a36Sopenharmony_ci	for (i = 0; i < 100; i++) {
18162306a36Sopenharmony_ci		/* This is incredibly slow (approx 20 ms) */
18262306a36Sopenharmony_ci		unsigned int status;
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci		usleep_range(1000, 3000);
18562306a36Sopenharmony_ci		status = inb(dev->iobase + DT2815_STATUS);
18662306a36Sopenharmony_ci		if (status == 4) {
18762306a36Sopenharmony_ci			unsigned int program;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci			program = (it->options[4] & 0x3) << 3 | 0x7;
19062306a36Sopenharmony_ci			outb(program, dev->iobase + DT2815_DATA);
19162306a36Sopenharmony_ci			dev_dbg(dev->class_dev, "program: 0x%x (@t=%d)\n",
19262306a36Sopenharmony_ci				program, i);
19362306a36Sopenharmony_ci			break;
19462306a36Sopenharmony_ci		} else if (status != 0x00) {
19562306a36Sopenharmony_ci			dev_dbg(dev->class_dev,
19662306a36Sopenharmony_ci				"unexpected status 0x%x (@t=%d)\n",
19762306a36Sopenharmony_ci				status, i);
19862306a36Sopenharmony_ci			if (status & 0x60)
19962306a36Sopenharmony_ci				outb(0x00, dev->iobase + DT2815_STATUS);
20062306a36Sopenharmony_ci		}
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	return 0;
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_cistatic struct comedi_driver dt2815_driver = {
20762306a36Sopenharmony_ci	.driver_name	= "dt2815",
20862306a36Sopenharmony_ci	.module		= THIS_MODULE,
20962306a36Sopenharmony_ci	.attach		= dt2815_attach,
21062306a36Sopenharmony_ci	.detach		= comedi_legacy_detach,
21162306a36Sopenharmony_ci};
21262306a36Sopenharmony_cimodule_comedi_driver(dt2815_driver);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ciMODULE_AUTHOR("Comedi https://www.comedi.org");
21562306a36Sopenharmony_ciMODULE_DESCRIPTION("Comedi low-level driver");
21662306a36Sopenharmony_ciMODULE_LICENSE("GPL");
217