1/*
2 * ni_6527.c
3 * Comedi driver for National Instruments PCI-6527
4 *
5 * COMEDI - Linux Control and Measurement Device Interface
6 * Copyright (C) 1999,2002,2003 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: ni_6527
21 * Description: National Instruments 6527
22 * Devices: (National Instruments) PCI-6527 [pci-6527]
23 *          (National Instruments) PXI-6527 [pxi-6527]
24 * Author: David A. Schleef <ds@schleef.org>
25 * Updated: Sat, 25 Jan 2003 13:24:40 -0800
26 * Status: works
27 *
28 * Configuration Options: not applicable, uses PCI auto config
29 */
30
31#include <linux/module.h>
32#include <linux/pci.h>
33#include <linux/interrupt.h>
34
35#include "../comedidev.h"
36
37#include "comedi_fc.h"
38
39/*
40 * PCI BAR1 - Register memory map
41 *
42 * Manuals (available from ftp://ftp.natinst.com/support/manuals)
43 *	370106b.pdf	6527 Register Level Programmer Manual
44 */
45#define NI6527_DI_REG(x)		(0x00 + (x))
46#define NI6527_DO_REG(x)		(0x03 + (x))
47#define NI6527_ID_REG			0x06
48#define NI6527_CLR_REG			0x07
49#define NI6527_CLR_EDGE			(1 << 3)
50#define NI6527_CLR_OVERFLOW		(1 << 2)
51#define NI6527_CLR_FILT			(1 << 1)
52#define NI6527_CLR_INTERVAL		(1 << 0)
53#define NI6527_CLR_IRQS			(NI6527_CLR_EDGE | NI6527_CLR_OVERFLOW)
54#define NI6527_CLR_RESET_FILT		(NI6527_CLR_FILT | NI6527_CLR_INTERVAL)
55#define NI6527_FILT_INTERVAL_REG(x)	(0x08 + (x))
56#define NI6527_FILT_ENA_REG(x)		(0x0c + (x))
57#define NI6527_STATUS_REG		0x14
58#define NI6527_STATUS_IRQ		(1 << 2)
59#define NI6527_STATUS_OVERFLOW		(1 << 1)
60#define NI6527_STATUS_EDGE		(1 << 0)
61#define NI6527_CTRL_REG			0x15
62#define NI6527_CTRL_FALLING		(1 << 4)
63#define NI6527_CTRL_RISING		(1 << 3)
64#define NI6527_CTRL_IRQ			(1 << 2)
65#define NI6527_CTRL_OVERFLOW		(1 << 1)
66#define NI6527_CTRL_EDGE		(1 << 0)
67#define NI6527_CTRL_DISABLE_IRQS	0
68#define NI6527_CTRL_ENABLE_IRQS		(NI6527_CTRL_FALLING | \
69					 NI6527_CTRL_RISING | \
70					 NI6527_CTRL_IRQ | NI6527_CTRL_EDGE)
71#define NI6527_RISING_EDGE_REG(x)	(0x18 + (x))
72#define NI6527_FALLING_EDGE_REG(x)	(0x20 + (x))
73
74enum ni6527_boardid {
75	BOARD_PCI6527,
76	BOARD_PXI6527,
77};
78
79struct ni6527_board {
80	const char *name;
81};
82
83static const struct ni6527_board ni6527_boards[] = {
84	[BOARD_PCI6527] = {
85		.name		= "pci-6527",
86	},
87	[BOARD_PXI6527] = {
88		.name		= "pxi-6527",
89	},
90};
91
92struct ni6527_private {
93	unsigned int filter_interval;
94	unsigned int filter_enable;
95};
96
97static void ni6527_set_filter_interval(struct comedi_device *dev,
98				       unsigned int val)
99{
100	struct ni6527_private *devpriv = dev->private;
101
102	if (val != devpriv->filter_interval) {
103		writeb(val & 0xff, dev->mmio + NI6527_FILT_INTERVAL_REG(0));
104		writeb((val >> 8) & 0xff,
105		       dev->mmio + NI6527_FILT_INTERVAL_REG(1));
106		writeb((val >> 16) & 0x0f,
107		       dev->mmio + NI6527_FILT_INTERVAL_REG(2));
108
109		writeb(NI6527_CLR_INTERVAL, dev->mmio + NI6527_CLR_REG);
110
111		devpriv->filter_interval = val;
112	}
113}
114
115static void ni6527_set_filter_enable(struct comedi_device *dev,
116				     unsigned int val)
117{
118	writeb(val & 0xff, dev->mmio + NI6527_FILT_ENA_REG(0));
119	writeb((val >> 8) & 0xff, dev->mmio + NI6527_FILT_ENA_REG(1));
120	writeb((val >> 16) & 0xff, dev->mmio + NI6527_FILT_ENA_REG(2));
121}
122
123static int ni6527_di_insn_config(struct comedi_device *dev,
124				 struct comedi_subdevice *s,
125				 struct comedi_insn *insn,
126				 unsigned int *data)
127{
128	struct ni6527_private *devpriv = dev->private;
129	unsigned int chan = CR_CHAN(insn->chanspec);
130	unsigned int interval;
131
132	switch (data[0]) {
133	case INSN_CONFIG_FILTER:
134		/*
135		 * The deglitch filter interval is specified in nanoseconds.
136		 * The hardware supports intervals in 200ns increments. Round
137		 * the user values up and return the actual interval.
138		 */
139		interval = (data[1] + 100) / 200;
140		data[1] = interval * 200;
141
142		if (interval) {
143			ni6527_set_filter_interval(dev, interval);
144			devpriv->filter_enable |= 1 << chan;
145		} else {
146			devpriv->filter_enable &= ~(1 << chan);
147		}
148		ni6527_set_filter_enable(dev, devpriv->filter_enable);
149		break;
150	default:
151		return -EINVAL;
152	}
153
154	return insn->n;
155}
156
157static int ni6527_di_insn_bits(struct comedi_device *dev,
158			       struct comedi_subdevice *s,
159			       struct comedi_insn *insn,
160			       unsigned int *data)
161{
162	unsigned int val;
163
164	val = readb(dev->mmio + NI6527_DI_REG(0));
165	val |= (readb(dev->mmio + NI6527_DI_REG(1)) << 8);
166	val |= (readb(dev->mmio + NI6527_DI_REG(2)) << 16);
167
168	data[1] = val;
169
170	return insn->n;
171}
172
173static int ni6527_do_insn_bits(struct comedi_device *dev,
174			       struct comedi_subdevice *s,
175			       struct comedi_insn *insn,
176			       unsigned int *data)
177{
178	unsigned int mask;
179
180	mask = comedi_dio_update_state(s, data);
181	if (mask) {
182		/* Outputs are inverted */
183		unsigned int val = s->state ^ 0xffffff;
184
185		if (mask & 0x0000ff)
186			writeb(val & 0xff, dev->mmio + NI6527_DO_REG(0));
187		if (mask & 0x00ff00)
188			writeb((val >> 8) & 0xff,
189			       dev->mmio + NI6527_DO_REG(1));
190		if (mask & 0xff0000)
191			writeb((val >> 16) & 0xff,
192			       dev->mmio + NI6527_DO_REG(2));
193	}
194
195	data[1] = s->state;
196
197	return insn->n;
198}
199
200static irqreturn_t ni6527_interrupt(int irq, void *d)
201{
202	struct comedi_device *dev = d;
203	struct comedi_subdevice *s = dev->read_subdev;
204	unsigned int status;
205
206	status = readb(dev->mmio + NI6527_STATUS_REG);
207	if (!(status & NI6527_STATUS_IRQ))
208		return IRQ_NONE;
209
210	if (status & NI6527_STATUS_EDGE) {
211		comedi_buf_put(s, 0);
212		s->async->events |= COMEDI_CB_EOS;
213		comedi_event(dev, s);
214	}
215
216	writeb(NI6527_CLR_IRQS, dev->mmio + NI6527_CLR_REG);
217
218	return IRQ_HANDLED;
219}
220
221static int ni6527_intr_cmdtest(struct comedi_device *dev,
222			       struct comedi_subdevice *s,
223			       struct comedi_cmd *cmd)
224{
225	int err = 0;
226
227	/* Step 1 : check if triggers are trivially valid */
228
229	err |= cfc_check_trigger_src(&cmd->start_src, TRIG_NOW);
230	err |= cfc_check_trigger_src(&cmd->scan_begin_src, TRIG_OTHER);
231	err |= cfc_check_trigger_src(&cmd->convert_src, TRIG_FOLLOW);
232	err |= cfc_check_trigger_src(&cmd->scan_end_src, TRIG_COUNT);
233	err |= cfc_check_trigger_src(&cmd->stop_src, TRIG_COUNT);
234
235	if (err)
236		return 1;
237
238	/* Step 2a : make sure trigger sources are unique */
239	/* Step 2b : and mutually compatible */
240
241	if (err)
242		return 2;
243
244	/* Step 3: check if arguments are trivially valid */
245
246	err |= cfc_check_trigger_arg_is(&cmd->start_arg, 0);
247	err |= cfc_check_trigger_arg_is(&cmd->scan_begin_arg, 0);
248	err |= cfc_check_trigger_arg_is(&cmd->convert_arg, 0);
249	err |= cfc_check_trigger_arg_is(&cmd->scan_end_arg, cmd->chanlist_len);
250	err |= cfc_check_trigger_arg_is(&cmd->stop_arg, 0);
251
252	if (err)
253		return 3;
254
255	/* Step 4: fix up any arguments */
256
257	/* Step 5: check channel list if it exists */
258
259	return 0;
260}
261
262static int ni6527_intr_cmd(struct comedi_device *dev,
263			   struct comedi_subdevice *s)
264{
265	writeb(NI6527_CLR_IRQS, dev->mmio + NI6527_CLR_REG);
266	writeb(NI6527_CTRL_ENABLE_IRQS, dev->mmio + NI6527_CTRL_REG);
267
268	return 0;
269}
270
271static int ni6527_intr_cancel(struct comedi_device *dev,
272			      struct comedi_subdevice *s)
273{
274	writeb(NI6527_CTRL_DISABLE_IRQS, dev->mmio + NI6527_CTRL_REG);
275
276	return 0;
277}
278
279static int ni6527_intr_insn_bits(struct comedi_device *dev,
280				 struct comedi_subdevice *s,
281				 struct comedi_insn *insn, unsigned int *data)
282{
283	data[1] = 0;
284	return insn->n;
285}
286
287static void ni6527_set_edge_detection(struct comedi_device *dev,
288				      unsigned int mask,
289				      unsigned int rising,
290				      unsigned int falling)
291{
292	unsigned int i;
293
294	rising &= mask;
295	falling &= mask;
296	for (i = 0; i < 2; i++) {
297		if (mask & 0xff) {
298			if (~mask & 0xff) {
299				/* preserve rising-edge detection channels */
300				rising |= readb(dev->mmio +
301						NI6527_RISING_EDGE_REG(i)) &
302					  (~mask & 0xff);
303				/* preserve falling-edge detection channels */
304				falling |= readb(dev->mmio +
305						 NI6527_FALLING_EDGE_REG(i)) &
306					   (~mask & 0xff);
307			}
308			/* update rising-edge detection channels */
309			writeb(rising & 0xff,
310			       dev->mmio + NI6527_RISING_EDGE_REG(i));
311			/* update falling-edge detection channels */
312			writeb(falling & 0xff,
313			       dev->mmio + NI6527_FALLING_EDGE_REG(i));
314		}
315		rising >>= 8;
316		falling >>= 8;
317		mask >>= 8;
318	}
319}
320
321static int ni6527_intr_insn_config(struct comedi_device *dev,
322				   struct comedi_subdevice *s,
323				   struct comedi_insn *insn,
324				   unsigned int *data)
325{
326	unsigned int mask = 0xffffffff;
327	unsigned int rising, falling, shift;
328
329	switch (data[0]) {
330	case INSN_CONFIG_CHANGE_NOTIFY:
331		/* check_insn_config_length() does not check this instruction */
332		if (insn->n != 3)
333			return -EINVAL;
334		rising = data[1];
335		falling = data[2];
336		ni6527_set_edge_detection(dev, mask, rising, falling);
337		break;
338	case INSN_CONFIG_DIGITAL_TRIG:
339		/* check trigger number */
340		if (data[1] != 0)
341			return -EINVAL;
342		/* check digital trigger operation */
343		switch (data[2]) {
344		case COMEDI_DIGITAL_TRIG_DISABLE:
345			rising = 0;
346			falling = 0;
347			break;
348		case COMEDI_DIGITAL_TRIG_ENABLE_EDGES:
349			/* check shift amount */
350			shift = data[3];
351			if (shift >= s->n_chan) {
352				mask = 0;
353				rising = 0;
354				falling = 0;
355			} else {
356				mask <<= shift;
357				rising = data[4] << shift;
358				falling = data[5] << shift;
359			}
360			break;
361		default:
362			return -EINVAL;
363		}
364		ni6527_set_edge_detection(dev, mask, rising, falling);
365		break;
366	default:
367		return -EINVAL;
368	}
369
370	return insn->n;
371}
372
373static void ni6527_reset(struct comedi_device *dev)
374{
375	/* disable deglitch filters on all channels */
376	ni6527_set_filter_enable(dev, 0);
377
378	/* disable edge detection */
379	ni6527_set_edge_detection(dev, 0xffffffff, 0, 0);
380
381	writeb(NI6527_CLR_IRQS | NI6527_CLR_RESET_FILT,
382	       dev->mmio + NI6527_CLR_REG);
383	writeb(NI6527_CTRL_DISABLE_IRQS, dev->mmio + NI6527_CTRL_REG);
384}
385
386static int ni6527_auto_attach(struct comedi_device *dev,
387			      unsigned long context)
388{
389	struct pci_dev *pcidev = comedi_to_pci_dev(dev);
390	const struct ni6527_board *board = NULL;
391	struct ni6527_private *devpriv;
392	struct comedi_subdevice *s;
393	int ret;
394
395	if (context < ARRAY_SIZE(ni6527_boards))
396		board = &ni6527_boards[context];
397	if (!board)
398		return -ENODEV;
399	dev->board_ptr = board;
400	dev->board_name = board->name;
401
402	devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv));
403	if (!devpriv)
404		return -ENOMEM;
405
406	ret = comedi_pci_enable(dev);
407	if (ret)
408		return ret;
409
410	dev->mmio = pci_ioremap_bar(pcidev, 1);
411	if (!dev->mmio)
412		return -ENOMEM;
413
414	/* make sure this is actually a 6527 device */
415	if (readb(dev->mmio + NI6527_ID_REG) != 0x27)
416		return -ENODEV;
417
418	ni6527_reset(dev);
419
420	ret = request_irq(pcidev->irq, ni6527_interrupt, IRQF_SHARED,
421			  dev->board_name, dev);
422	if (ret == 0)
423		dev->irq = pcidev->irq;
424
425	ret = comedi_alloc_subdevices(dev, 3);
426	if (ret)
427		return ret;
428
429	/* Digital Input subdevice */
430	s = &dev->subdevices[0];
431	s->type		= COMEDI_SUBD_DI;
432	s->subdev_flags	= SDF_READABLE;
433	s->n_chan	= 24;
434	s->maxdata	= 1;
435	s->range_table	= &range_digital;
436	s->insn_config	= ni6527_di_insn_config;
437	s->insn_bits	= ni6527_di_insn_bits;
438
439	/* Digital Output subdevice */
440	s = &dev->subdevices[1];
441	s->type		= COMEDI_SUBD_DO;
442	s->subdev_flags	= SDF_WRITABLE;
443	s->n_chan	= 24;
444	s->maxdata	= 1;
445	s->range_table	= &range_digital;
446	s->insn_bits	= ni6527_do_insn_bits;
447
448	/* Edge detection interrupt subdevice */
449	s = &dev->subdevices[2];
450	if (dev->irq) {
451		dev->read_subdev = s;
452		s->type		= COMEDI_SUBD_DI;
453		s->subdev_flags	= SDF_READABLE | SDF_CMD_READ;
454		s->n_chan	= 1;
455		s->maxdata	= 1;
456		s->range_table	= &range_digital;
457		s->insn_config	= ni6527_intr_insn_config;
458		s->insn_bits	= ni6527_intr_insn_bits;
459		s->len_chanlist	= 1;
460		s->do_cmdtest	= ni6527_intr_cmdtest;
461		s->do_cmd	= ni6527_intr_cmd;
462		s->cancel	= ni6527_intr_cancel;
463	} else {
464		s->type = COMEDI_SUBD_UNUSED;
465	}
466
467	return 0;
468}
469
470static void ni6527_detach(struct comedi_device *dev)
471{
472	if (dev->mmio)
473		ni6527_reset(dev);
474	comedi_pci_detach(dev);
475}
476
477static struct comedi_driver ni6527_driver = {
478	.driver_name	= "ni_6527",
479	.module		= THIS_MODULE,
480	.auto_attach	= ni6527_auto_attach,
481	.detach		= ni6527_detach,
482};
483
484static int ni6527_pci_probe(struct pci_dev *dev,
485			    const struct pci_device_id *id)
486{
487	return comedi_pci_auto_config(dev, &ni6527_driver, id->driver_data);
488}
489
490static const struct pci_device_id ni6527_pci_table[] = {
491	{ PCI_VDEVICE(NI, 0x2b10), BOARD_PXI6527 },
492	{ PCI_VDEVICE(NI, 0x2b20), BOARD_PCI6527 },
493	{ 0 }
494};
495MODULE_DEVICE_TABLE(pci, ni6527_pci_table);
496
497static struct pci_driver ni6527_pci_driver = {
498	.name		= "ni_6527",
499	.id_table	= ni6527_pci_table,
500	.probe		= ni6527_pci_probe,
501	.remove		= comedi_pci_auto_unconfig,
502};
503module_comedi_pci_driver(ni6527_driver, ni6527_pci_driver);
504
505MODULE_AUTHOR("Comedi http://www.comedi.org");
506MODULE_DESCRIPTION("Comedi driver for National Instruments PCI-6527");
507MODULE_LICENSE("GPL");
508