adl_pci6208.c revision 7b5f9dbcd8eb5de65756a2b1c89d2d5adfb933d2
1/*
2    comedi/drivers/adl_pci6208.c
3
4    Hardware driver for ADLink 6208 series cards:
5	card	     | voltage output    | current output
6	-------------+-------------------+---------------
7	PCI-6208V    |  8 channels       | -
8	PCI-6216V    | 16 channels       | -
9	PCI-6208A    |  8 channels       | 8 channels
10
11    COMEDI - Linux Control and Measurement Device Interface
12    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
13
14    This program is free software; you can redistribute it and/or modify
15    it under the terms of the GNU General Public License as published by
16    the Free Software Foundation; either version 2 of the License, or
17    (at your option) any later version.
18
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22    GNU General Public License for more details.
23*/
24/*
25Driver: adl_pci6208
26Description: ADLink PCI-6208/6216 Series Multi-channel Analog Output Cards
27Devices: (ADLink) PCI-6208 [adl_pci6208]
28	 (ADLink) PCI-6216 [adl_pci6216]
29Author: nsyeow <nsyeow@pd.jaring.my>
30Updated: Fri, 30 Jan 2004 14:44:27 +0800
31Status: untested
32
33Configuration Options: not applicable, uses PCI auto config
34
35References:
36	- ni_660x.c
37	- adl_pci9111.c		copied the entire pci setup section
38	- adl_pci9118.c
39*/
40
41#include <linux/module.h>
42#include <linux/delay.h>
43#include <linux/pci.h>
44
45#include "../comedidev.h"
46
47/*
48 * PCI-6208/6216-GL register map
49 */
50#define PCI6208_AO_CONTROL(x)		(0x00 + (2 * (x)))
51#define PCI6208_AO_STATUS		0x00
52#define PCI6208_AO_STATUS_DATA_SEND	(1 << 0)
53#define PCI6208_DIO			0x40
54#define PCI6208_DIO_DO_MASK		(0x0f)
55#define PCI6208_DIO_DO_SHIFT		(0)
56#define PCI6208_DIO_DI_MASK		(0xf0)
57#define PCI6208_DIO_DI_SHIFT		(4)
58
59#define PCI6208_MAX_AO_CHANNELS		16
60
61enum pci6208_boardid {
62	BOARD_PCI6208,
63	BOARD_PCI6216,
64};
65
66struct pci6208_board {
67	const char *name;
68	int ao_chans;
69};
70
71static const struct pci6208_board pci6208_boards[] = {
72	[BOARD_PCI6208] = {
73		.name		= "adl_pci6208",
74		.ao_chans	= 8,
75	},
76	[BOARD_PCI6216] = {
77		.name		= "adl_pci6216",
78		.ao_chans	= 16,
79	},
80};
81
82struct pci6208_private {
83	unsigned int ao_readback[PCI6208_MAX_AO_CHANNELS];
84};
85
86static int pci6208_ao_wait_for_data_send(struct comedi_device *dev,
87					 unsigned int timeout)
88{
89	unsigned int status;
90
91	while (timeout--) {
92		status = inw(dev->iobase + PCI6208_AO_STATUS);
93		if ((status & PCI6208_AO_STATUS_DATA_SEND) == 0)
94			return 0;
95		udelay(1);
96	}
97
98	return -ETIME;
99}
100
101static int pci6208_ao_insn_write(struct comedi_device *dev,
102				 struct comedi_subdevice *s,
103				 struct comedi_insn *insn,
104				 unsigned int *data)
105{
106	struct pci6208_private *devpriv = dev->private;
107	unsigned int chan = CR_CHAN(insn->chanspec);
108	unsigned int val = devpriv->ao_readback[chan];
109	int ret;
110	int i;
111
112	for (i = 0; i < insn->n; i++) {
113		val = data[i];
114
115		/* D/A transfer rate is 2.2us, wait up to 10us */
116		ret = pci6208_ao_wait_for_data_send(dev, 10);
117		if (ret)
118			return ret;
119
120		/* the hardware expects two's complement values */
121		outw(comedi_offset_munge(s, val),
122		     dev->iobase + PCI6208_AO_CONTROL(chan));
123	}
124	devpriv->ao_readback[chan] = val;
125
126	return insn->n;
127}
128
129static int pci6208_ao_insn_read(struct comedi_device *dev,
130				struct comedi_subdevice *s,
131				struct comedi_insn *insn,
132				unsigned int *data)
133{
134	struct pci6208_private *devpriv = dev->private;
135	unsigned int chan = CR_CHAN(insn->chanspec);
136	int i;
137
138	for (i = 0; i < insn->n; i++)
139		data[i] = devpriv->ao_readback[chan];
140
141	return insn->n;
142}
143
144static int pci6208_di_insn_bits(struct comedi_device *dev,
145				struct comedi_subdevice *s,
146				struct comedi_insn *insn,
147				unsigned int *data)
148{
149	unsigned int val;
150
151	val = inw(dev->iobase + PCI6208_DIO);
152	val = (val & PCI6208_DIO_DI_MASK) >> PCI6208_DIO_DI_SHIFT;
153
154	data[1] = val;
155
156	return insn->n;
157}
158
159static int pci6208_do_insn_bits(struct comedi_device *dev,
160				struct comedi_subdevice *s,
161				struct comedi_insn *insn,
162				unsigned int *data)
163{
164	if (comedi_dio_update_state(s, data))
165		outw(s->state, dev->iobase + PCI6208_DIO);
166
167	data[1] = s->state;
168
169	return insn->n;
170}
171
172static int pci6208_auto_attach(struct comedi_device *dev,
173			       unsigned long context)
174{
175	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
176	const struct pci6208_board *boardinfo = NULL;
177	struct pci6208_private *devpriv;
178	struct comedi_subdevice *s;
179	unsigned int val;
180	int ret;
181
182	if (context < ARRAY_SIZE(pci6208_boards))
183		boardinfo = &pci6208_boards[context];
184	if (!boardinfo)
185		return -ENODEV;
186	dev->board_ptr = boardinfo;
187	dev->board_name = boardinfo->name;
188
189	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
190	if (!devpriv)
191		return -ENOMEM;
192
193	ret = comedi_pci_enable(dev);
194	if (ret)
195		return ret;
196	dev->iobase = pci_resource_start(pcidev, 2);
197
198	ret = comedi_alloc_subdevices(dev, 3);
199	if (ret)
200		return ret;
201
202	s = &dev->subdevices[0];
203	/* analog output subdevice */
204	s->type		= COMEDI_SUBD_AO;
205	s->subdev_flags	= SDF_WRITABLE;
206	s->n_chan	= boardinfo->ao_chans;
207	s->maxdata	= 0xffff;
208	s->range_table	= &range_bipolar10;
209	s->insn_write	= pci6208_ao_insn_write;
210	s->insn_read	= pci6208_ao_insn_read;
211
212	s = &dev->subdevices[1];
213	/* digital input subdevice */
214	s->type		= COMEDI_SUBD_DI;
215	s->subdev_flags	= SDF_READABLE;
216	s->n_chan	= 4;
217	s->maxdata	= 1;
218	s->range_table	= &range_digital;
219	s->insn_bits	= pci6208_di_insn_bits;
220
221	s = &dev->subdevices[2];
222	/* digital output subdevice */
223	s->type		= COMEDI_SUBD_DO;
224	s->subdev_flags	= SDF_WRITABLE;
225	s->n_chan	= 4;
226	s->maxdata	= 1;
227	s->range_table	= &range_digital;
228	s->insn_bits	= pci6208_do_insn_bits;
229
230	/*
231	 * Get the read back signals from the digital outputs
232	 * and save it as the initial state for the subdevice.
233	 */
234	val = inw(dev->iobase + PCI6208_DIO);
235	val = (val & PCI6208_DIO_DO_MASK) >> PCI6208_DIO_DO_SHIFT;
236	s->state	= val;
237
238	return 0;
239}
240
241static struct comedi_driver adl_pci6208_driver = {
242	.driver_name	= "adl_pci6208",
243	.module		= THIS_MODULE,
244	.auto_attach	= pci6208_auto_attach,
245	.detach		= comedi_pci_disable,
246};
247
248static int adl_pci6208_pci_probe(struct pci_dev *dev,
249				 const struct pci_device_id *id)
250{
251	return comedi_pci_auto_config(dev, &adl_pci6208_driver,
252				      id->driver_data);
253}
254
255static DEFINE_PCI_DEVICE_TABLE(adl_pci6208_pci_table) = {
256	{ PCI_VDEVICE(ADLINK, 0x6208), BOARD_PCI6208 },
257	{ PCI_VDEVICE(ADLINK, 0x6216), BOARD_PCI6216 },
258	{ 0 }
259};
260MODULE_DEVICE_TABLE(pci, adl_pci6208_pci_table);
261
262static struct pci_driver adl_pci6208_pci_driver = {
263	.name		= "adl_pci6208",
264	.id_table	= adl_pci6208_pci_table,
265	.probe		= adl_pci6208_pci_probe,
266	.remove		= comedi_pci_auto_unconfig,
267};
268module_comedi_pci_driver(adl_pci6208_driver, adl_pci6208_pci_driver);
269
270MODULE_AUTHOR("Comedi http://www.comedi.org");
271MODULE_DESCRIPTION("Comedi low-level driver");
272MODULE_LICENSE("GPL");
273