1/*
2    comedi/drivers/adv_pci1724.c
3    This is a driver for the Advantech PCI-1724U card.
4
5    Author:  Frank Mori Hess <fmh6jj@gmail.com>
6    Copyright (C) 2013 GnuBIO Inc
7
8    COMEDI - Linux Control and Measurement Device Interface
9    Copyright (C) 1997-8 David A. Schleef <ds@schleef.org>
10
11    This program is free software; you can redistribute it and/or modify
12    it under the terms of the GNU General Public License as published by
13    the Free Software Foundation; either version 2 of the License, or
14    (at your option) any later version.
15
16    This program is distributed in the hope that it will be useful,
17    but WITHOUT ANY WARRANTY; without even the implied warranty of
18    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19    GNU General Public License for more details.
20*/
21
22/*
23
24Driver: adv_1724
25Description: Advantech PCI-1724U
26Author: Frank Mori Hess <fmh6jj@gmail.com>
27Status: works
28Updated: 2013-02-09
29Devices: [Advantech] PCI-1724U (adv_pci1724)
30
31Subdevice 0 is the analog output.
32Subdevice 1 is the offset calibration for the analog output.
33Subdevice 2 is the gain calibration for the analog output.
34
35The calibration offset and gains have quite a large effect
36on the analog output, so it is possible to adjust the analog output to
37have an output range significantly different from the board's
38nominal output ranges.  For a calibrated +/- 10V range, the analog
39output's offset will be set somewhere near mid-range (0x2000) and its
40gain will be near maximum (0x3fff).
41
42There is really no difference between the board's documented 0-20mA
43versus 4-20mA output ranges.  To pick one or the other is simply a matter
44of adjusting the offset and gain calibration until the board outputs in
45the desired range.
46
47Configuration options:
48   None
49
50Manual configuration of comedi devices is not supported by this driver;
51supported PCI devices are configured as comedi devices automatically.
52
53*/
54
55#include <linux/module.h>
56#include <linux/delay.h>
57#include <linux/pci.h>
58
59#include "../comedidev.h"
60
61#define PCI_VENDOR_ID_ADVANTECH	0x13fe
62
63#define NUM_AO_CHANNELS 32
64
65/* register offsets */
66enum board_registers {
67	DAC_CONTROL_REG = 0x0,
68	SYNC_OUTPUT_REG = 0x4,
69	EEPROM_CONTROL_REG = 0x8,
70	SYNC_OUTPUT_TRIGGER_REG = 0xc,
71	BOARD_ID_REG = 0x10
72};
73
74/* bit definitions for registers */
75enum dac_control_contents {
76	DAC_DATA_MASK = 0x3fff,
77	DAC_DESTINATION_MASK = 0xc000,
78	DAC_NORMAL_MODE = 0xc000,
79	DAC_OFFSET_MODE = 0x8000,
80	DAC_GAIN_MODE = 0x4000,
81	DAC_CHANNEL_SELECT_MASK = 0xf0000,
82	DAC_GROUP_SELECT_MASK = 0xf00000
83};
84
85static uint32_t dac_data_bits(uint16_t dac_data)
86{
87	return dac_data & DAC_DATA_MASK;
88}
89
90static uint32_t dac_channel_select_bits(unsigned channel)
91{
92	return (channel << 16) & DAC_CHANNEL_SELECT_MASK;
93}
94
95static uint32_t dac_group_select_bits(unsigned group)
96{
97	return (1 << (20 + group)) & DAC_GROUP_SELECT_MASK;
98}
99
100static uint32_t dac_channel_and_group_select_bits(unsigned comedi_channel)
101{
102	return dac_channel_select_bits(comedi_channel % 8) |
103		dac_group_select_bits(comedi_channel / 8);
104}
105
106enum sync_output_contents {
107	SYNC_MODE = 0x1,
108	DAC_BUSY = 0x2, /* dac state machine is not ready */
109};
110
111enum sync_output_trigger_contents {
112	SYNC_TRIGGER_BITS = 0x0 /* any value works */
113};
114
115enum board_id_contents {
116	BOARD_ID_MASK = 0xf
117};
118
119static const struct comedi_lrange ao_ranges_1724 = {
120	4, {
121		BIP_RANGE(10),
122		RANGE_mA(0, 20),
123		RANGE_mA(4, 20),
124		RANGE_unitless(0, 1)
125	}
126};
127
128/* this structure is for data unique to this hardware driver. */
129struct adv_pci1724_private {
130	int ao_value[NUM_AO_CHANNELS];
131	int offset_value[NUM_AO_CHANNELS];
132	int gain_value[NUM_AO_CHANNELS];
133};
134
135static int wait_for_dac_idle(struct comedi_device *dev)
136{
137	static const int timeout = 10000;
138	int i;
139
140	for (i = 0; i < timeout; ++i) {
141		if ((inl(dev->iobase + SYNC_OUTPUT_REG) & DAC_BUSY) == 0)
142			break;
143		udelay(1);
144	}
145	if (i == timeout) {
146		dev_err(dev->class_dev,
147			"Timed out waiting for dac to become idle\n");
148		return -EIO;
149	}
150	return 0;
151}
152
153static int set_dac(struct comedi_device *dev, unsigned mode, unsigned channel,
154		   unsigned data)
155{
156	int retval;
157	unsigned control_bits;
158
159	retval = wait_for_dac_idle(dev);
160	if (retval < 0)
161		return retval;
162
163	control_bits = mode;
164	control_bits |= dac_channel_and_group_select_bits(channel);
165	control_bits |= dac_data_bits(data);
166	outl(control_bits, dev->iobase + DAC_CONTROL_REG);
167	return 0;
168}
169
170static int ao_winsn(struct comedi_device *dev, struct comedi_subdevice *s,
171		    struct comedi_insn *insn, unsigned int *data)
172{
173	struct adv_pci1724_private *devpriv = dev->private;
174	int channel = CR_CHAN(insn->chanspec);
175	int retval;
176	int i;
177
178	/* turn off synchronous mode */
179	outl(0, dev->iobase + SYNC_OUTPUT_REG);
180
181	for (i = 0; i < insn->n; ++i) {
182		retval = set_dac(dev, DAC_NORMAL_MODE, channel, data[i]);
183		if (retval < 0)
184			return retval;
185		devpriv->ao_value[channel] = data[i];
186	}
187	return insn->n;
188}
189
190static int ao_readback_insn(struct comedi_device *dev,
191			    struct comedi_subdevice *s,
192			    struct comedi_insn *insn, unsigned int *data)
193{
194	struct adv_pci1724_private *devpriv = dev->private;
195	int channel = CR_CHAN(insn->chanspec);
196	int i;
197
198	if (devpriv->ao_value[channel] < 0) {
199		dev_err(dev->class_dev,
200			"Cannot read back channels which have not yet been written to\n");
201		return -EIO;
202	}
203	for (i = 0; i < insn->n; i++)
204		data[i] = devpriv->ao_value[channel];
205
206	return insn->n;
207}
208
209static int offset_write_insn(struct comedi_device *dev,
210			     struct comedi_subdevice *s,
211			     struct comedi_insn *insn, unsigned int *data)
212{
213	struct adv_pci1724_private *devpriv = dev->private;
214	int channel = CR_CHAN(insn->chanspec);
215	int retval;
216	int i;
217
218	/* turn off synchronous mode */
219	outl(0, dev->iobase + SYNC_OUTPUT_REG);
220
221	for (i = 0; i < insn->n; ++i) {
222		retval = set_dac(dev, DAC_OFFSET_MODE, channel, data[i]);
223		if (retval < 0)
224			return retval;
225		devpriv->offset_value[channel] = data[i];
226	}
227
228	return insn->n;
229}
230
231static int offset_read_insn(struct comedi_device *dev,
232			    struct comedi_subdevice *s,
233			    struct comedi_insn *insn, unsigned int *data)
234{
235	struct adv_pci1724_private *devpriv = dev->private;
236	unsigned int channel = CR_CHAN(insn->chanspec);
237	int i;
238
239	if (devpriv->offset_value[channel] < 0) {
240		dev_err(dev->class_dev,
241			"Cannot read back channels which have not yet been written to\n");
242		return -EIO;
243	}
244	for (i = 0; i < insn->n; i++)
245		data[i] = devpriv->offset_value[channel];
246
247	return insn->n;
248}
249
250static int gain_write_insn(struct comedi_device *dev,
251			   struct comedi_subdevice *s,
252			   struct comedi_insn *insn, unsigned int *data)
253{
254	struct adv_pci1724_private *devpriv = dev->private;
255	int channel = CR_CHAN(insn->chanspec);
256	int retval;
257	int i;
258
259	/* turn off synchronous mode */
260	outl(0, dev->iobase + SYNC_OUTPUT_REG);
261
262	for (i = 0; i < insn->n; ++i) {
263		retval = set_dac(dev, DAC_GAIN_MODE, channel, data[i]);
264		if (retval < 0)
265			return retval;
266		devpriv->gain_value[channel] = data[i];
267	}
268
269	return insn->n;
270}
271
272static int gain_read_insn(struct comedi_device *dev,
273			  struct comedi_subdevice *s, struct comedi_insn *insn,
274			  unsigned int *data)
275{
276	struct adv_pci1724_private *devpriv = dev->private;
277	unsigned int channel = CR_CHAN(insn->chanspec);
278	int i;
279
280	if (devpriv->gain_value[channel] < 0) {
281		dev_err(dev->class_dev,
282			"Cannot read back channels which have not yet been written to\n");
283		return -EIO;
284	}
285	for (i = 0; i < insn->n; i++)
286		data[i] = devpriv->gain_value[channel];
287
288	return insn->n;
289}
290
291/* Allocate and initialize the subdevice structures.
292 */
293static int setup_subdevices(struct comedi_device *dev)
294{
295	struct comedi_subdevice *s;
296	int ret;
297
298	ret = comedi_alloc_subdevices(dev, 3);
299	if (ret)
300		return ret;
301
302	/* analog output subdevice */
303	s = &dev->subdevices[0];
304	s->type = COMEDI_SUBD_AO;
305	s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_GROUND;
306	s->n_chan = NUM_AO_CHANNELS;
307	s->maxdata = 0x3fff;
308	s->range_table = &ao_ranges_1724;
309	s->insn_read = ao_readback_insn;
310	s->insn_write = ao_winsn;
311
312	/* offset calibration */
313	s = &dev->subdevices[1];
314	s->type = COMEDI_SUBD_CALIB;
315	s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
316	s->n_chan = NUM_AO_CHANNELS;
317	s->insn_read = offset_read_insn;
318	s->insn_write = offset_write_insn;
319	s->maxdata = 0x3fff;
320
321	/* gain calibration */
322	s = &dev->subdevices[2];
323	s->type = COMEDI_SUBD_CALIB;
324	s->subdev_flags = SDF_READABLE | SDF_WRITABLE | SDF_INTERNAL;
325	s->n_chan = NUM_AO_CHANNELS;
326	s->insn_read = gain_read_insn;
327	s->insn_write = gain_write_insn;
328	s->maxdata = 0x3fff;
329
330	return 0;
331}
332
333static int adv_pci1724_auto_attach(struct comedi_device *dev,
334				   unsigned long context_unused)
335{
336	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
337	struct adv_pci1724_private *devpriv;
338	int i;
339	int retval;
340	unsigned int board_id;
341
342	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
343	if (!devpriv)
344		return -ENOMEM;
345
346	/* init software copies of output values to indicate we don't know
347	 * what the output value is since it has never been written. */
348	for (i = 0; i < NUM_AO_CHANNELS; ++i) {
349		devpriv->ao_value[i] = -1;
350		devpriv->offset_value[i] = -1;
351		devpriv->gain_value[i] = -1;
352	}
353
354	retval = comedi_pci_enable(dev);
355	if (retval)
356		return retval;
357
358	dev->iobase = pci_resource_start(pcidev, 2);
359	board_id = inl(dev->iobase + BOARD_ID_REG) & BOARD_ID_MASK;
360	dev_info(dev->class_dev, "board id: %d\n", board_id);
361
362	retval = setup_subdevices(dev);
363	if (retval < 0)
364		return retval;
365
366	dev_info(dev->class_dev, "%s (pci %s) attached, board id: %u\n",
367		 dev->board_name, pci_name(pcidev), board_id);
368	return 0;
369}
370
371static struct comedi_driver adv_pci1724_driver = {
372	.driver_name = "adv_pci1724",
373	.module = THIS_MODULE,
374	.auto_attach = adv_pci1724_auto_attach,
375	.detach = comedi_pci_detach,
376};
377
378static int adv_pci1724_pci_probe(struct pci_dev *dev,
379				 const struct pci_device_id *id)
380{
381	return comedi_pci_auto_config(dev, &adv_pci1724_driver,
382				      id->driver_data);
383}
384
385static const struct pci_device_id adv_pci1724_pci_table[] = {
386	{ PCI_DEVICE(PCI_VENDOR_ID_ADVANTECH, 0x1724) },
387	{ 0 }
388};
389MODULE_DEVICE_TABLE(pci, adv_pci1724_pci_table);
390
391static struct pci_driver adv_pci1724_pci_driver = {
392	.name = "adv_pci1724",
393	.id_table = adv_pci1724_pci_table,
394	.probe = adv_pci1724_pci_probe,
395	.remove = comedi_pci_auto_unconfig,
396};
397
398module_comedi_pci_driver(adv_pci1724_driver, adv_pci1724_pci_driver);
399
400MODULE_AUTHOR("Frank Mori Hess <fmh6jj@gmail.com>");
401MODULE_DESCRIPTION("Advantech PCI-1724U Comedi driver");
402MODULE_LICENSE("GPL");
403