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