1/*
2 * COMEDI driver for the ADLINK PCI-723x/743x series boards.
3 * Copyright (C) 2012 H Hartley Sweeten <hsweeten@visionengravers.com>
4 *
5 * Based on the adl_pci7230 driver written by:
6 *	David Fernandez <dfcastelao@gmail.com>
7 * and the adl_pci7432 driver written by:
8 *	Michel Lachaine <mike@mikelachaine.ca>
9 *
10 * COMEDI - Linux Control and Measurement Device Interface
11 * Copyright (C) 2000 David A. Schleef <ds@schleef.org>
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 2 of the License, or
16 * (at your option) any later version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 * GNU General Public License for more details.
22 */
23
24/*
25Driver: adl_pci7x3x
26Description: 32/64-Channel Isolated Digital I/O Boards
27Devices: (ADLink) PCI-7230 [adl_pci7230] - 16 input / 16 output
28	 (ADLink) PCI-7233 [adl_pci7233] - 32 input
29	 (ADLink) PCI-7234 [adl_pci7234] - 32 output
30	 (ADLink) PCI-7432 [adl_pci7432] - 32 input / 32 output
31	 (ADLink) PCI-7433 [adl_pci7433] - 64 input
32	 (ADLink) PCI-7434 [adl_pci7434] - 64 output
33Author: H Hartley Sweeten <hsweeten@visionengravers.com>
34Updated: Thu, 02 Aug 2012 14:27:46 -0700
35Status: untested
36
37The PCI-7230, PCI-7432 and PCI-7433 boards also support external
38interrupt signals on digital input channels 0 and 1. The PCI-7233
39has dual-interrupt sources for change-of-state (COS) on any 16
40digital input channels of LSB and for COS on any 16 digital input
41lines of MSB. Interrupts are not currently supported by this
42driver.
43
44Configuration Options: not applicable, uses comedi PCI auto config
45*/
46
47#include <linux/module.h>
48#include <linux/pci.h>
49
50#include "../comedidev.h"
51
52/*
53 * Register I/O map (32-bit access only)
54 */
55#define PCI7X3X_DIO_REG		0x00
56#define PCI743X_DIO_REG		0x04
57
58enum apci1516_boardid {
59	BOARD_PCI7230,
60	BOARD_PCI7233,
61	BOARD_PCI7234,
62	BOARD_PCI7432,
63	BOARD_PCI7433,
64	BOARD_PCI7434,
65};
66
67struct adl_pci7x3x_boardinfo {
68	const char *name;
69	int nsubdevs;
70	int di_nchan;
71	int do_nchan;
72};
73
74static const struct adl_pci7x3x_boardinfo adl_pci7x3x_boards[] = {
75	[BOARD_PCI7230] = {
76		.name		= "adl_pci7230",
77		.nsubdevs	= 2,
78		.di_nchan	= 16,
79		.do_nchan	= 16,
80	},
81	[BOARD_PCI7233] = {
82		.name		= "adl_pci7233",
83		.nsubdevs	= 1,
84		.di_nchan	= 32,
85	},
86	[BOARD_PCI7234] = {
87		.name		= "adl_pci7234",
88		.nsubdevs	= 1,
89		.do_nchan	= 32,
90	},
91	[BOARD_PCI7432] = {
92		.name		= "adl_pci7432",
93		.nsubdevs	= 2,
94		.di_nchan	= 32,
95		.do_nchan	= 32,
96	},
97	[BOARD_PCI7433] = {
98		.name		= "adl_pci7433",
99		.nsubdevs	= 2,
100		.di_nchan	= 64,
101	},
102	[BOARD_PCI7434] = {
103		.name		= "adl_pci7434",
104		.nsubdevs	= 2,
105		.do_nchan	= 64,
106	}
107};
108
109static int adl_pci7x3x_do_insn_bits(struct comedi_device *dev,
110				    struct comedi_subdevice *s,
111				    struct comedi_insn *insn,
112				    unsigned int *data)
113{
114	unsigned long reg = (unsigned long)s->private;
115
116	if (comedi_dio_update_state(s, data))
117		outl(s->state, dev->iobase + reg);
118
119	data[1] = s->state;
120
121	return insn->n;
122}
123
124static int adl_pci7x3x_di_insn_bits(struct comedi_device *dev,
125				    struct comedi_subdevice *s,
126				    struct comedi_insn *insn,
127				    unsigned int *data)
128{
129	unsigned long reg = (unsigned long)s->private;
130
131	data[1] = inl(dev->iobase + reg);
132
133	return insn->n;
134}
135
136static int adl_pci7x3x_auto_attach(struct comedi_device *dev,
137				   unsigned long context)
138{
139	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
140	const struct adl_pci7x3x_boardinfo *board = NULL;
141	struct comedi_subdevice *s;
142	int subdev;
143	int nchan;
144	int ret;
145
146	if (context < ARRAY_SIZE(adl_pci7x3x_boards))
147		board = &adl_pci7x3x_boards[context];
148	if (!board)
149		return -ENODEV;
150	dev->board_ptr = board;
151	dev->board_name = board->name;
152
153	ret = comedi_pci_enable(dev);
154	if (ret)
155		return ret;
156	dev->iobase = pci_resource_start(pcidev, 2);
157
158	/*
159	 * One or two subdevices are setup by this driver depending on
160	 * the number of digital inputs and/or outputs provided by the
161	 * board. Each subdevice has a maximum of 32 channels.
162	 *
163	 *	PCI-7230 - 2 subdevices: 0 - 16 input, 1 - 16 output
164	 *	PCI-7233 - 1 subdevice: 0 - 32 input
165	 *	PCI-7234 - 1 subdevice: 0 - 32 output
166	 *	PCI-7432 - 2 subdevices: 0 - 32 input, 1 - 32 output
167	 *	PCI-7433 - 2 subdevices: 0 - 32 input, 1 - 32 input
168	 *	PCI-7434 - 2 subdevices: 0 - 32 output, 1 - 32 output
169	 */
170	ret = comedi_alloc_subdevices(dev, board->nsubdevs);
171	if (ret)
172		return ret;
173
174	subdev = 0;
175
176	if (board->di_nchan) {
177		nchan = min(board->di_nchan, 32);
178
179		s = &dev->subdevices[subdev];
180		/* Isolated digital inputs 0 to 15/31 */
181		s->type		= COMEDI_SUBD_DI;
182		s->subdev_flags	= SDF_READABLE;
183		s->n_chan	= nchan;
184		s->maxdata	= 1;
185		s->insn_bits	= adl_pci7x3x_di_insn_bits;
186		s->range_table	= &range_digital;
187
188		s->private	= (void *)PCI7X3X_DIO_REG;
189
190		subdev++;
191
192		nchan = board->di_nchan - nchan;
193		if (nchan) {
194			s = &dev->subdevices[subdev];
195			/* Isolated digital inputs 32 to 63 */
196			s->type		= COMEDI_SUBD_DI;
197			s->subdev_flags	= SDF_READABLE;
198			s->n_chan	= nchan;
199			s->maxdata	= 1;
200			s->insn_bits	= adl_pci7x3x_di_insn_bits;
201			s->range_table	= &range_digital;
202
203			s->private	= (void *)PCI743X_DIO_REG;
204
205			subdev++;
206		}
207	}
208
209	if (board->do_nchan) {
210		nchan = min(board->do_nchan, 32);
211
212		s = &dev->subdevices[subdev];
213		/* Isolated digital outputs 0 to 15/31 */
214		s->type		= COMEDI_SUBD_DO;
215		s->subdev_flags	= SDF_WRITABLE;
216		s->n_chan	= nchan;
217		s->maxdata	= 1;
218		s->insn_bits	= adl_pci7x3x_do_insn_bits;
219		s->range_table	= &range_digital;
220
221		s->private	= (void *)PCI7X3X_DIO_REG;
222
223		subdev++;
224
225		nchan = board->do_nchan - nchan;
226		if (nchan) {
227			s = &dev->subdevices[subdev];
228			/* Isolated digital outputs 32 to 63 */
229			s->type		= COMEDI_SUBD_DO;
230			s->subdev_flags	= SDF_WRITABLE;
231			s->n_chan	= nchan;
232			s->maxdata	= 1;
233			s->insn_bits	= adl_pci7x3x_do_insn_bits;
234			s->range_table	= &range_digital;
235
236			s->private	= (void *)PCI743X_DIO_REG;
237
238			subdev++;
239		}
240	}
241
242	return 0;
243}
244
245static struct comedi_driver adl_pci7x3x_driver = {
246	.driver_name	= "adl_pci7x3x",
247	.module		= THIS_MODULE,
248	.auto_attach	= adl_pci7x3x_auto_attach,
249	.detach		= comedi_pci_detach,
250};
251
252static int adl_pci7x3x_pci_probe(struct pci_dev *dev,
253				 const struct pci_device_id *id)
254{
255	return comedi_pci_auto_config(dev, &adl_pci7x3x_driver,
256				      id->driver_data);
257}
258
259static const struct pci_device_id adl_pci7x3x_pci_table[] = {
260	{ PCI_VDEVICE(ADLINK, 0x7230), BOARD_PCI7230 },
261	{ PCI_VDEVICE(ADLINK, 0x7233), BOARD_PCI7233 },
262	{ PCI_VDEVICE(ADLINK, 0x7234), BOARD_PCI7234 },
263	{ PCI_VDEVICE(ADLINK, 0x7432), BOARD_PCI7432 },
264	{ PCI_VDEVICE(ADLINK, 0x7433), BOARD_PCI7433 },
265	{ PCI_VDEVICE(ADLINK, 0x7434), BOARD_PCI7434 },
266	{ 0 }
267};
268MODULE_DEVICE_TABLE(pci, adl_pci7x3x_pci_table);
269
270static struct pci_driver adl_pci7x3x_pci_driver = {
271	.name		= "adl_pci7x3x",
272	.id_table	= adl_pci7x3x_pci_table,
273	.probe		= adl_pci7x3x_pci_probe,
274	.remove		= comedi_pci_auto_unconfig,
275};
276module_comedi_pci_driver(adl_pci7x3x_driver, adl_pci7x3x_pci_driver);
277
278MODULE_DESCRIPTION("ADLINK PCI-723x/743x Isolated Digital I/O boards");
279MODULE_AUTHOR("H Hartley Sweeten <hsweeten@visionengravers.com>");
280MODULE_LICENSE("GPL");
281