adl_pci6208.c revision b609a5959f5ee44342d08ad37faba11bfbeb8b92
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_winsn(struct comedi_device *dev,
102			    struct comedi_subdevice *s,
103			    struct comedi_insn *insn, unsigned int *data)
104{
105	struct pci6208_private *devpriv = dev->private;
106	int chan = CR_CHAN(insn->chanspec);
107	unsigned int invert = 1 << (16 - 1);
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		outw(val ^ invert, dev->iobase + PCI6208_AO_CONTROL(chan));
121	}
122	devpriv->ao_readback[chan] = val;
123
124	return insn->n;
125}
126
127static int pci6208_ao_rinsn(struct comedi_device *dev,
128			    struct comedi_subdevice *s,
129			    struct comedi_insn *insn, unsigned int *data)
130{
131	struct pci6208_private *devpriv = dev->private;
132	int chan = CR_CHAN(insn->chanspec);
133	int i;
134
135	for (i = 0; i < insn->n; i++)
136		data[i] = devpriv->ao_readback[chan];
137
138	return insn->n;
139}
140
141static int pci6208_di_insn_bits(struct comedi_device *dev,
142				struct comedi_subdevice *s,
143				struct comedi_insn *insn,
144				unsigned int *data)
145{
146	unsigned int val;
147
148	val = inw(dev->iobase + PCI6208_DIO);
149	val = (val & PCI6208_DIO_DI_MASK) >> PCI6208_DIO_DI_SHIFT;
150
151	data[1] = val;
152
153	return insn->n;
154}
155
156static int pci6208_do_insn_bits(struct comedi_device *dev,
157				struct comedi_subdevice *s,
158				struct comedi_insn *insn,
159				unsigned int *data)
160{
161	if (comedi_dio_update_state(s, data))
162		outw(s->state, dev->iobase + PCI6208_DIO);
163
164	data[1] = s->state;
165
166	return insn->n;
167}
168
169static int pci6208_auto_attach(struct comedi_device *dev,
170			       unsigned long context)
171{
172	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
173	const struct pci6208_board *boardinfo = NULL;
174	struct pci6208_private *devpriv;
175	struct comedi_subdevice *s;
176	unsigned int val;
177	int ret;
178
179	if (context < ARRAY_SIZE(pci6208_boards))
180		boardinfo = &pci6208_boards[context];
181	if (!boardinfo)
182		return -ENODEV;
183	dev->board_ptr = boardinfo;
184	dev->board_name = boardinfo->name;
185
186	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
187	if (!devpriv)
188		return -ENOMEM;
189
190	ret = comedi_pci_enable(dev);
191	if (ret)
192		return ret;
193	dev->iobase = pci_resource_start(pcidev, 2);
194
195	ret = comedi_alloc_subdevices(dev, 3);
196	if (ret)
197		return ret;
198
199	s = &dev->subdevices[0];
200	/* analog output subdevice */
201	s->type		= COMEDI_SUBD_AO;
202	s->subdev_flags	= SDF_WRITABLE;
203	s->n_chan	= boardinfo->ao_chans;
204	s->maxdata	= 0xffff;
205	s->range_table	= &range_bipolar10;
206	s->insn_write	= pci6208_ao_winsn;
207	s->insn_read	= pci6208_ao_rinsn;
208
209	s = &dev->subdevices[1];
210	/* digital input subdevice */
211	s->type		= COMEDI_SUBD_DI;
212	s->subdev_flags	= SDF_READABLE;
213	s->n_chan	= 4;
214	s->maxdata	= 1;
215	s->range_table	= &range_digital;
216	s->insn_bits	= pci6208_di_insn_bits;
217
218	s = &dev->subdevices[2];
219	/* digital output subdevice */
220	s->type		= COMEDI_SUBD_DO;
221	s->subdev_flags	= SDF_WRITABLE;
222	s->n_chan	= 4;
223	s->maxdata	= 1;
224	s->range_table	= &range_digital;
225	s->insn_bits	= pci6208_do_insn_bits;
226
227	/*
228	 * Get the read back signals from the digital outputs
229	 * and save it as the initial state for the subdevice.
230	 */
231	val = inw(dev->iobase + PCI6208_DIO);
232	val = (val & PCI6208_DIO_DO_MASK) >> PCI6208_DIO_DO_SHIFT;
233	s->state	= val;
234
235	dev_info(dev->class_dev, "%s: %s, I/O base=0x%04lx\n",
236		dev->driver->driver_name, dev->board_name, dev->iobase);
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