1#include <linux/module.h>
2#include <linux/pci.h>
3
4#include "../comedidev.h"
5#include "comedi_fc.h"
6#include "amcc_s5933.h"
7
8#include "addi-data/addi_common.h"
9
10#include "addi-data/hwdrv_apci3120.c"
11
12enum apci3120_boardid {
13	BOARD_APCI3120,
14	BOARD_APCI3001,
15};
16
17static const struct addi_board apci3120_boardtypes[] = {
18	[BOARD_APCI3120] = {
19		.pc_DriverName		= "apci3120",
20		.i_NbrAiChannel		= 16,
21		.i_NbrAiChannelDiff	= 8,
22		.i_AiChannelList	= 16,
23		.i_NbrAoChannel		= 8,
24		.i_AiMaxdata		= 0xffff,
25		.i_AoMaxdata		= 0x3fff,
26		.i_NbrDiChannel		= 4,
27		.i_NbrDoChannel		= 4,
28		.i_DoMaxdata		= 0x0f,
29		.interrupt		= apci3120_interrupt,
30	},
31	[BOARD_APCI3001] = {
32		.pc_DriverName		= "apci3001",
33		.i_NbrAiChannel		= 16,
34		.i_NbrAiChannelDiff	= 8,
35		.i_AiChannelList	= 16,
36		.i_AiMaxdata		= 0xfff,
37		.i_NbrDiChannel		= 4,
38		.i_NbrDoChannel		= 4,
39		.i_DoMaxdata		= 0x0f,
40		.interrupt		= apci3120_interrupt,
41	},
42};
43
44static irqreturn_t v_ADDI_Interrupt(int irq, void *d)
45{
46	struct comedi_device *dev = d;
47	const struct addi_board *this_board = dev->board_ptr;
48
49	this_board->interrupt(irq, d);
50	return IRQ_RETVAL(1);
51}
52
53static int apci3120_auto_attach(struct comedi_device *dev,
54				unsigned long context)
55{
56	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
57	const struct addi_board *this_board = NULL;
58	struct addi_private *devpriv;
59	struct comedi_subdevice *s;
60	int ret, order, i;
61
62	if (context < ARRAY_SIZE(apci3120_boardtypes))
63		this_board = &apci3120_boardtypes[context];
64	if (!this_board)
65		return -ENODEV;
66	dev->board_ptr = this_board;
67	dev->board_name = this_board->pc_DriverName;
68
69	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
70	if (!devpriv)
71		return -ENOMEM;
72
73	ret = comedi_pci_enable(dev);
74	if (ret)
75		return ret;
76	pci_set_master(pcidev);
77
78	dev->iobase = pci_resource_start(pcidev, 1);
79	devpriv->iobase = dev->iobase;
80	devpriv->i_IobaseAmcc = pci_resource_start(pcidev, 0);
81	devpriv->i_IobaseAddon = pci_resource_start(pcidev, 2);
82	devpriv->i_IobaseReserved = pci_resource_start(pcidev, 3);
83
84	if (pcidev->irq > 0) {
85		ret = request_irq(pcidev->irq, v_ADDI_Interrupt, IRQF_SHARED,
86				  dev->board_name, dev);
87		if (ret == 0)
88			dev->irq = pcidev->irq;
89	}
90
91	/* Allocate DMA buffers */
92	for (i = 0; i < 2; i++) {
93		for (order = 2; order >= 0; order--) {
94			devpriv->ul_DmaBufferVirtual[i] =
95			    dma_alloc_coherent(dev->hw_dev, PAGE_SIZE << order,
96					       &devpriv->ul_DmaBufferHw[i],
97					       GFP_KERNEL);
98
99			if (devpriv->ul_DmaBufferVirtual[i])
100				break;
101		}
102		if (!devpriv->ul_DmaBufferVirtual[i])
103			break;
104		devpriv->ui_DmaBufferSize[i] = PAGE_SIZE << order;
105	}
106	if (devpriv->ul_DmaBufferVirtual[0])
107		devpriv->us_UseDma = 1;
108
109	if (devpriv->ul_DmaBufferVirtual[1])
110		devpriv->b_DmaDoubleBuffer = 1;
111
112	ret = comedi_alloc_subdevices(dev, 5);
113	if (ret)
114		return ret;
115
116	/*  Allocate and Initialise AI Subdevice Structures */
117	s = &dev->subdevices[0];
118	dev->read_subdev = s;
119	s->type = COMEDI_SUBD_AI;
120	s->subdev_flags =
121		SDF_READABLE | SDF_COMMON | SDF_GROUND
122		| SDF_DIFF;
123	if (this_board->i_NbrAiChannel)
124		s->n_chan = this_board->i_NbrAiChannel;
125	else
126		s->n_chan = this_board->i_NbrAiChannelDiff;
127	s->maxdata = this_board->i_AiMaxdata;
128	s->len_chanlist = this_board->i_AiChannelList;
129	s->range_table = &range_apci3120_ai;
130
131	s->insn_config = apci3120_ai_insn_config;
132	s->insn_read = apci3120_ai_insn_read;
133	s->do_cmdtest = apci3120_ai_cmdtest;
134	s->do_cmd = apci3120_ai_cmd;
135	s->cancel = apci3120_cancel;
136
137	/*  Allocate and Initialise AO Subdevice Structures */
138	s = &dev->subdevices[1];
139	if (this_board->i_NbrAoChannel) {
140		s->type = COMEDI_SUBD_AO;
141		s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
142		s->n_chan = this_board->i_NbrAoChannel;
143		s->maxdata = this_board->i_AoMaxdata;
144		s->len_chanlist = this_board->i_NbrAoChannel;
145		s->range_table = &range_apci3120_ao;
146		s->insn_write = apci3120_ao_insn_write;
147	} else {
148		s->type = COMEDI_SUBD_UNUSED;
149	}
150
151	/*  Allocate and Initialise DI Subdevice Structures */
152	s = &dev->subdevices[2];
153	s->type = COMEDI_SUBD_DI;
154	s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_COMMON;
155	s->n_chan = this_board->i_NbrDiChannel;
156	s->maxdata = 1;
157	s->len_chanlist = this_board->i_NbrDiChannel;
158	s->range_table = &range_digital;
159	s->insn_bits = apci3120_di_insn_bits;
160
161	/*  Allocate and Initialise DO Subdevice Structures */
162	s = &dev->subdevices[3];
163	s->type = COMEDI_SUBD_DO;
164	s->subdev_flags =
165		SDF_READABLE | SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
166	s->n_chan = this_board->i_NbrDoChannel;
167	s->maxdata = this_board->i_DoMaxdata;
168	s->len_chanlist = this_board->i_NbrDoChannel;
169	s->range_table = &range_digital;
170	s->insn_bits = apci3120_do_insn_bits;
171
172	/*  Allocate and Initialise Timer Subdevice Structures */
173	s = &dev->subdevices[4];
174	s->type = COMEDI_SUBD_TIMER;
175	s->subdev_flags = SDF_WRITEABLE | SDF_GROUND | SDF_COMMON;
176	s->n_chan = 1;
177	s->maxdata = 0;
178	s->len_chanlist = 1;
179	s->range_table = &range_digital;
180
181	s->insn_write = apci3120_write_insn_timer;
182	s->insn_read = apci3120_read_insn_timer;
183	s->insn_config = apci3120_config_insn_timer;
184
185	apci3120_reset(dev);
186	return 0;
187}
188
189static void apci3120_detach(struct comedi_device *dev)
190{
191	struct addi_private *devpriv = dev->private;
192
193	if (dev->iobase)
194		apci3120_reset(dev);
195	comedi_pci_detach(dev);
196	if (devpriv) {
197		unsigned int i;
198
199		for (i = 0; i < 2; i++) {
200			if (devpriv->ul_DmaBufferVirtual[i]) {
201				dma_free_coherent(dev->hw_dev,
202						  devpriv->ui_DmaBufferSize[i],
203						  devpriv->
204						  ul_DmaBufferVirtual[i],
205						  devpriv->ul_DmaBufferHw[i]);
206			}
207		}
208	}
209}
210
211static struct comedi_driver apci3120_driver = {
212	.driver_name	= "addi_apci_3120",
213	.module		= THIS_MODULE,
214	.auto_attach	= apci3120_auto_attach,
215	.detach		= apci3120_detach,
216};
217
218static int apci3120_pci_probe(struct pci_dev *dev,
219			      const struct pci_device_id *id)
220{
221	return comedi_pci_auto_config(dev, &apci3120_driver, id->driver_data);
222}
223
224static const struct pci_device_id apci3120_pci_table[] = {
225	{ PCI_VDEVICE(AMCC, 0x818d), BOARD_APCI3120 },
226	{ PCI_VDEVICE(AMCC, 0x828d), BOARD_APCI3001 },
227	{ 0 }
228};
229MODULE_DEVICE_TABLE(pci, apci3120_pci_table);
230
231static struct pci_driver apci3120_pci_driver = {
232	.name		= "addi_apci_3120",
233	.id_table	= apci3120_pci_table,
234	.probe		= apci3120_pci_probe,
235	.remove		= comedi_pci_auto_unconfig,
236};
237module_comedi_pci_driver(apci3120_driver, apci3120_pci_driver);
238
239MODULE_AUTHOR("Comedi http://www.comedi.org");
240MODULE_DESCRIPTION("ADDI-DATA APCI-3120, Analog input board");
241MODULE_LICENSE("GPL");
242