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