adl_pci6208.c revision 20fb170b293218ef66ce885df3a77903e3d11572
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/pci.h>
43
44#include "../comedidev.h"
45
46/*
47 * PCI-6208/6216-GL register map
48 */
49#define PCI6208_AO_CONTROL(x)		(0x00 + (2 * (x)))
50#define PCI6208_AO_STATUS		0x00
51#define PCI6208_AO_STATUS_DATA_SEND	(1 << 0)
52#define PCI6208_DIO			0x40
53#define PCI6208_DIO_DO_MASK		(0x0f)
54#define PCI6208_DIO_DO_SHIFT		(0)
55#define PCI6208_DIO_DI_MASK		(0xf0)
56#define PCI6208_DIO_DI_SHIFT		(4)
57
58#define PCI6208_MAX_AO_CHANNELS		16
59
60enum pci6208_boardid {
61	BOARD_PCI6208,
62	BOARD_PCI6216,
63};
64
65struct pci6208_board {
66	const char *name;
67	int ao_chans;
68};
69
70static const struct pci6208_board pci6208_boards[] = {
71	[BOARD_PCI6208] = {
72		.name		= "adl_pci6208",
73		.ao_chans	= 8,
74	},
75	[BOARD_PCI6216] = {
76		.name		= "adl_pci6216",
77		.ao_chans	= 16,
78	},
79};
80
81struct pci6208_private {
82	unsigned int ao_readback[PCI6208_MAX_AO_CHANNELS];
83};
84
85static int pci6208_ao_winsn(struct comedi_device *dev,
86			    struct comedi_subdevice *s,
87			    struct comedi_insn *insn, unsigned int *data)
88{
89	struct pci6208_private *devpriv = dev->private;
90	int chan = CR_CHAN(insn->chanspec);
91	unsigned int invert = 1 << (16 - 1);
92	unsigned int val = devpriv->ao_readback[chan];
93	unsigned short status;
94	int i;
95
96	for (i = 0; i < insn->n; i++) {
97		val = data[i];
98
99		do {
100			status = inw(dev->iobase + PCI6208_AO_STATUS);
101		} while (status & PCI6208_AO_STATUS_DATA_SEND);
102
103		outw(val ^ invert, dev->iobase + PCI6208_AO_CONTROL(chan));
104	}
105	devpriv->ao_readback[chan] = val;
106
107	return insn->n;
108}
109
110static int pci6208_ao_rinsn(struct comedi_device *dev,
111			    struct comedi_subdevice *s,
112			    struct comedi_insn *insn, unsigned int *data)
113{
114	struct pci6208_private *devpriv = dev->private;
115	int chan = CR_CHAN(insn->chanspec);
116	int i;
117
118	for (i = 0; i < insn->n; i++)
119		data[i] = devpriv->ao_readback[chan];
120
121	return insn->n;
122}
123
124static int pci6208_di_insn_bits(struct comedi_device *dev,
125				struct comedi_subdevice *s,
126				struct comedi_insn *insn,
127				unsigned int *data)
128{
129	unsigned int val;
130
131	val = inw(dev->iobase + PCI6208_DIO);
132	val = (val & PCI6208_DIO_DI_MASK) >> PCI6208_DIO_DI_SHIFT;
133
134	data[1] = val;
135
136	return insn->n;
137}
138
139static int pci6208_do_insn_bits(struct comedi_device *dev,
140				struct comedi_subdevice *s,
141				struct comedi_insn *insn,
142				unsigned int *data)
143{
144	if (comedi_dio_update_state(s, data))
145		outw(s->state, dev->iobase + PCI6208_DIO);
146
147	data[1] = s->state;
148
149	return insn->n;
150}
151
152static int pci6208_auto_attach(struct comedi_device *dev,
153			       unsigned long context)
154{
155	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
156	const struct pci6208_board *boardinfo = NULL;
157	struct pci6208_private *devpriv;
158	struct comedi_subdevice *s;
159	unsigned int val;
160	int ret;
161
162	if (context < ARRAY_SIZE(pci6208_boards))
163		boardinfo = &pci6208_boards[context];
164	if (!boardinfo)
165		return -ENODEV;
166	dev->board_ptr = boardinfo;
167	dev->board_name = boardinfo->name;
168
169	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
170	if (!devpriv)
171		return -ENOMEM;
172
173	ret = comedi_pci_enable(dev);
174	if (ret)
175		return ret;
176	dev->iobase = pci_resource_start(pcidev, 2);
177
178	ret = comedi_alloc_subdevices(dev, 3);
179	if (ret)
180		return ret;
181
182	s = &dev->subdevices[0];
183	/* analog output subdevice */
184	s->type		= COMEDI_SUBD_AO;
185	s->subdev_flags	= SDF_WRITABLE;
186	s->n_chan	= boardinfo->ao_chans;
187	s->maxdata	= 0xffff;
188	s->range_table	= &range_bipolar10;
189	s->insn_write	= pci6208_ao_winsn;
190	s->insn_read	= pci6208_ao_rinsn;
191
192	s = &dev->subdevices[1];
193	/* digital input subdevice */
194	s->type		= COMEDI_SUBD_DI;
195	s->subdev_flags	= SDF_READABLE;
196	s->n_chan	= 4;
197	s->maxdata	= 1;
198	s->range_table	= &range_digital;
199	s->insn_bits	= pci6208_di_insn_bits;
200
201	s = &dev->subdevices[2];
202	/* digital output subdevice */
203	s->type		= COMEDI_SUBD_DO;
204	s->subdev_flags	= SDF_WRITABLE;
205	s->n_chan	= 4;
206	s->maxdata	= 1;
207	s->range_table	= &range_digital;
208	s->insn_bits	= pci6208_do_insn_bits;
209
210	/*
211	 * Get the read back signals from the digital outputs
212	 * and save it as the initial state for the subdevice.
213	 */
214	val = inw(dev->iobase + PCI6208_DIO);
215	val = (val & PCI6208_DIO_DO_MASK) >> PCI6208_DIO_DO_SHIFT;
216	s->state	= val;
217
218	dev_info(dev->class_dev, "%s: %s, I/O base=0x%04lx\n",
219		dev->driver->driver_name, dev->board_name, dev->iobase);
220
221	return 0;
222}
223
224static struct comedi_driver adl_pci6208_driver = {
225	.driver_name	= "adl_pci6208",
226	.module		= THIS_MODULE,
227	.auto_attach	= pci6208_auto_attach,
228	.detach		= comedi_pci_disable,
229};
230
231static int adl_pci6208_pci_probe(struct pci_dev *dev,
232				 const struct pci_device_id *id)
233{
234	return comedi_pci_auto_config(dev, &adl_pci6208_driver,
235				      id->driver_data);
236}
237
238static DEFINE_PCI_DEVICE_TABLE(adl_pci6208_pci_table) = {
239	{ PCI_VDEVICE(ADLINK, 0x6208), BOARD_PCI6208 },
240	{ PCI_VDEVICE(ADLINK, 0x6216), BOARD_PCI6216 },
241	{ 0 }
242};
243MODULE_DEVICE_TABLE(pci, adl_pci6208_pci_table);
244
245static struct pci_driver adl_pci6208_pci_driver = {
246	.name		= "adl_pci6208",
247	.id_table	= adl_pci6208_pci_table,
248	.probe		= adl_pci6208_pci_probe,
249	.remove		= comedi_pci_auto_unconfig,
250};
251module_comedi_pci_driver(adl_pci6208_driver, adl_pci6208_pci_driver);
252
253MODULE_AUTHOR("Comedi http://www.comedi.org");
254MODULE_DESCRIPTION("Comedi low-level driver");
255MODULE_LICENSE("GPL");
256