aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/comedi/drivers/pcmda12.c
blob: 59108c06cedc3d007aec5b9a015fc26570cf7fca (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
 * pcmda12.c
 * Driver for Winsystems PC-104 based PCM-D/A-12 8-channel AO board.
 *
 * COMEDI - Linux Control and Measurement Device Interface
 * Copyright (C) 2006 Calin A. Culianu <calin@ajvar.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.
 */

/*
 * Driver: pcmda12
 * Description: A driver for the Winsystems PCM-D/A-12
 * Devices: (Winsystems) PCM-D/A-12 [pcmda12]
 * Author: Calin Culianu <calin@ajvar.org>
 * Updated: Fri, 13 Jan 2006 12:01:01 -0500
 * Status: works
 *
 * A driver for the relatively straightforward-to-program PCM-D/A-12.
 * This board doesn't support commands, and the only way to set its
 * analog output range is to jumper the board. As such,
 * comedi_data_write() ignores the range value specified.
 *
 * The board uses 16 consecutive I/O addresses starting at the I/O port
 * base address. Each address corresponds to the LSB then MSB of a
 * particular channel from 0-7.
 *
 * Note that the board is not ISA-PNP capable and thus needs the I/O
 * port comedi_config parameter.
 *
 * Note that passing a nonzero value as the second config option will
 * enable "simultaneous xfer" mode for this board, in which AO writes
 * will not take effect until a subsequent read of any AO channel. This
 * is so that one can speed up programming by preloading all AO registers
 * with values before simultaneously setting them to take effect with one
 * read command.
 *
 * Configuration Options:
 *   [0] - I/O port base address
 *   [1] - Do Simultaneous Xfer (see description)
 */

#include <linux/module.h>
#include "../comedidev.h"

/* AI range is not configurable, it's set by jumpers on the board */
static const struct comedi_lrange pcmda12_ranges = {
	3, {
		UNI_RANGE(5),
		UNI_RANGE(10),
		BIP_RANGE(5)
	}
};

struct pcmda12_private {
	int simultaneous_xfer_mode;
};

static int pcmda12_ao_insn_write(struct comedi_device *dev,
				 struct comedi_subdevice *s,
				 struct comedi_insn *insn,
				 unsigned int *data)
{
	struct pcmda12_private *devpriv = dev->private;
	unsigned int chan = CR_CHAN(insn->chanspec);
	unsigned int val = s->readback[chan];
	unsigned long ioreg = dev->iobase + (chan * 2);
	int i;

	for (i = 0; i < insn->n; ++i) {
		val = data[i];
		outb(val & 0xff, ioreg);
		outb((val >> 8) & 0xff, ioreg + 1);

		/*
		 * Initiate transfer if not in simultaneaous xfer
		 * mode by reading one of the AO registers.
		 */
		if (!devpriv->simultaneous_xfer_mode)
			inb(ioreg);
	}
	s->readback[chan] = val;

	return insn->n;
}

static int pcmda12_ao_insn_read(struct comedi_device *dev,
				struct comedi_subdevice *s,
				struct comedi_insn *insn,
				unsigned int *data)
{
	struct pcmda12_private *devpriv = dev->private;

	/*
	 * Initiate simultaneaous xfer mode by reading one of the
	 * AO registers. All analog outputs will then be updated.
	 */
	if (devpriv->simultaneous_xfer_mode)
		inb(dev->iobase);

	return comedi_readback_insn_read(dev, s, insn, data);
}

static void pcmda12_ao_reset(struct comedi_device *dev,
			     struct comedi_subdevice *s)
{
	int i;

	for (i = 0; i < s->n_chan; ++i) {
		outb(0, dev->iobase + (i * 2));
		outb(0, dev->iobase + (i * 2) + 1);
	}
	/* Initiate transfer by reading one of the AO registers. */
	inb(dev->iobase);
}

static int pcmda12_attach(struct comedi_device *dev,
			  struct comedi_devconfig *it)
{
	struct pcmda12_private *devpriv;
	struct comedi_subdevice *s;
	int ret;

	ret = comedi_request_region(dev, it->options[0], 0x10);
	if (ret)
		return ret;

	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
	if (!devpriv)
		return -ENOMEM;

	devpriv->simultaneous_xfer_mode = it->options[1];

	ret = comedi_alloc_subdevices(dev, 1);
	if (ret)
		return ret;

	s = &dev->subdevices[0];
	s->type		= COMEDI_SUBD_AO;
	s->subdev_flags	= SDF_READABLE | SDF_WRITABLE;
	s->n_chan	= 8;
	s->maxdata	= 0x0fff;
	s->range_table	= &pcmda12_ranges;
	s->insn_write	= pcmda12_ao_insn_write;
	s->insn_read	= pcmda12_ao_insn_read;

	ret = comedi_alloc_subdev_readback(s);
	if (ret)
		return ret;

	pcmda12_ao_reset(dev, s);

	return 0;
}

static struct comedi_driver pcmda12_driver = {
	.driver_name	= "pcmda12",
	.module		= THIS_MODULE,
	.attach		= pcmda12_attach,
	.detach		= comedi_legacy_detach,
};
module_comedi_driver(pcmda12_driver);

MODULE_AUTHOR("Comedi http://www.comedi.org");
MODULE_DESCRIPTION("Comedi low-level driver");
MODULE_LICENSE("GPL");