comedi_parport.c revision 7114a28011f9d5f3d981731ad341177c21f9d948
1/*
2    comedi/drivers/comedi_parport.c
3    hardware driver for standard parallel port
4
5    COMEDI - Linux Control and Measurement Device Interface
6    Copyright (C) 1998,2001 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    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21
22*/
23/*
24Driver: comedi_parport
25Description: Standard PC parallel port
26Author: ds
27Status: works in immediate mode
28Devices: [standard] parallel port (comedi_parport)
29Updated: Tue, 30 Apr 2002 21:11:45 -0700
30
31A cheap and easy way to get a few more digital I/O lines.  Steal
32additional parallel ports from old computers or your neighbors'
33computers.
34
35Option list:
36 0: I/O port base for the parallel port.
37 1: IRQ
38
39Parallel Port Lines:
40
41pin     subdev  chan    aka
42---     ------  ----    ---
431       2       0       strobe
442       0       0       data 0
453       0       1       data 1
464       0       2       data 2
475       0       3       data 3
486       0       4       data 4
497       0       5       data 5
508       0       6       data 6
519       0       7       data 7
5210      1       3       acknowledge
5311      1       4       busy
5412      1       2       output
5513      1       1       printer selected
5614      2       1       auto LF
5715      1       0       error
5816      2       2       init
5917      2       3       select printer
6018-25   ground
61
62Notes:
63
64Subdevices 0 is digital I/O, subdevice 1 is digital input, and
65subdevice 2 is digital output.  Unlike other Comedi devices,
66subdevice 0 defaults to output.
67
68Pins 13 and 14 are inverted once by Comedi and once by the
69hardware, thus cancelling the effect.
70
71Pin 1 is a strobe, thus acts like one.  There's no way in software
72to change this, at least on a standard parallel port.
73
74Subdevice 3 pretends to be a digital input subdevice, but it always
75returns 0 when read.  However, if you run a command with
76scan_begin_src=TRIG_EXT, it uses pin 10 as a external triggering
77pin, which can be used to wake up tasks.
78*/
79/*
80   see http://www.beyondlogic.org/ for information.
81   or http://www.linux-magazin.de/ausgabe/1999/10/IO/io.html
82 */
83
84#include "../comedidev.h"
85#include <linux/interrupt.h>
86#include <linux/ioport.h>
87
88#define PARPORT_SIZE 3
89
90#define PARPORT_A 0
91#define PARPORT_B 1
92#define PARPORT_C 2
93
94static int parport_attach(struct comedi_device *dev,
95			  struct comedi_devconfig *it);
96static int parport_detach(struct comedi_device *dev);
97static struct comedi_driver driver_parport = {
98	.driver_name = "comedi_parport",
99	.module = THIS_MODULE,
100	.attach = parport_attach,
101	.detach = parport_detach,
102};
103
104static int __init driver_parport_init_module(void)
105{
106	return comedi_driver_register(&driver_parport);
107}
108
109static void __exit driver_parport_cleanup_module(void)
110{
111	comedi_driver_unregister(&driver_parport);
112}
113
114module_init(driver_parport_init_module);
115module_exit(driver_parport_cleanup_module);
116
117struct parport_private {
118	unsigned int a_data;
119	unsigned int c_data;
120	int enable_irq;
121};
122#define devpriv ((struct parport_private *)(dev->private))
123
124static int parport_insn_a(struct comedi_device *dev, struct comedi_subdevice *s,
125			  struct comedi_insn *insn, unsigned int *data)
126{
127	if (data[0]) {
128		devpriv->a_data &= ~data[0];
129		devpriv->a_data |= (data[0] & data[1]);
130
131		outb(devpriv->a_data, dev->iobase + PARPORT_A);
132	}
133
134	data[1] = inb(dev->iobase + PARPORT_A);
135
136	return 2;
137}
138
139static int parport_insn_config_a(struct comedi_device *dev,
140				 struct comedi_subdevice *s,
141				 struct comedi_insn *insn, unsigned int *data)
142{
143	if (data[0]) {
144		s->io_bits = 0xff;
145		devpriv->c_data &= ~(1 << 5);
146	} else {
147		s->io_bits = 0;
148		devpriv->c_data |= (1 << 5);
149	}
150	outb(devpriv->c_data, dev->iobase + PARPORT_C);
151
152	return 1;
153}
154
155static int parport_insn_b(struct comedi_device *dev, struct comedi_subdevice *s,
156			  struct comedi_insn *insn, unsigned int *data)
157{
158	if (data[0]) {
159		/* should writes be ignored? */
160		/* anyone??? */
161	}
162
163	data[1] = (inb(dev->iobase + PARPORT_B) >> 3);
164
165	return 2;
166}
167
168static int parport_insn_c(struct comedi_device *dev, struct comedi_subdevice *s,
169			  struct comedi_insn *insn, unsigned int *data)
170{
171	data[0] &= 0x0f;
172	if (data[0]) {
173		devpriv->c_data &= ~data[0];
174		devpriv->c_data |= (data[0] & data[1]);
175
176		outb(devpriv->c_data, dev->iobase + PARPORT_C);
177	}
178
179	data[1] = devpriv->c_data & 0xf;
180
181	return 2;
182}
183
184static int parport_intr_insn(struct comedi_device *dev,
185			     struct comedi_subdevice *s,
186			     struct comedi_insn *insn, unsigned int *data)
187{
188	if (insn->n < 1)
189		return -EINVAL;
190
191	data[1] = 0;
192	return 2;
193}
194
195static int parport_intr_cmdtest(struct comedi_device *dev,
196				struct comedi_subdevice *s,
197				struct comedi_cmd *cmd)
198{
199	int err = 0;
200	int tmp;
201
202	/* step 1 */
203
204	tmp = cmd->start_src;
205	cmd->start_src &= TRIG_NOW;
206	if (!cmd->start_src || tmp != cmd->start_src)
207		err++;
208
209	tmp = cmd->scan_begin_src;
210	cmd->scan_begin_src &= TRIG_EXT;
211	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
212		err++;
213
214	tmp = cmd->convert_src;
215	cmd->convert_src &= TRIG_FOLLOW;
216	if (!cmd->convert_src || tmp != cmd->convert_src)
217		err++;
218
219	tmp = cmd->scan_end_src;
220	cmd->scan_end_src &= TRIG_COUNT;
221	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
222		err++;
223
224	tmp = cmd->stop_src;
225	cmd->stop_src &= TRIG_NONE;
226	if (!cmd->stop_src || tmp != cmd->stop_src)
227		err++;
228
229	if (err)
230		return 1;
231
232	/* step 2: ignored */
233
234	if (err)
235		return 2;
236
237	/* step 3: */
238
239	if (cmd->start_arg != 0) {
240		cmd->start_arg = 0;
241		err++;
242	}
243	if (cmd->scan_begin_arg != 0) {
244		cmd->scan_begin_arg = 0;
245		err++;
246	}
247	if (cmd->convert_arg != 0) {
248		cmd->convert_arg = 0;
249		err++;
250	}
251	if (cmd->scan_end_arg != 1) {
252		cmd->scan_end_arg = 1;
253		err++;
254	}
255	if (cmd->stop_arg != 0) {
256		cmd->stop_arg = 0;
257		err++;
258	}
259
260	if (err)
261		return 3;
262
263	/* step 4: ignored */
264
265	if (err)
266		return 4;
267
268	return 0;
269}
270
271static int parport_intr_cmd(struct comedi_device *dev,
272			    struct comedi_subdevice *s)
273{
274	devpriv->c_data |= 0x10;
275	outb(devpriv->c_data, dev->iobase + PARPORT_C);
276
277	devpriv->enable_irq = 1;
278
279	return 0;
280}
281
282static int parport_intr_cancel(struct comedi_device *dev,
283			       struct comedi_subdevice *s)
284{
285	printk(KERN_DEBUG "parport_intr_cancel()\n");
286
287	devpriv->c_data &= ~0x10;
288	outb(devpriv->c_data, dev->iobase + PARPORT_C);
289
290	devpriv->enable_irq = 0;
291
292	return 0;
293}
294
295static irqreturn_t parport_interrupt(int irq, void *d)
296{
297	struct comedi_device *dev = d;
298	struct comedi_subdevice *s = dev->subdevices + 3;
299
300	if (!devpriv->enable_irq) {
301		printk(KERN_ERR "comedi_parport: bogus irq, ignored\n");
302		return IRQ_NONE;
303	}
304
305	comedi_buf_put(s->async, 0);
306	s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
307
308	comedi_event(dev, s);
309	return IRQ_HANDLED;
310}
311
312static int parport_attach(struct comedi_device *dev,
313			  struct comedi_devconfig *it)
314{
315	int ret;
316	unsigned int irq;
317	unsigned long iobase;
318	struct comedi_subdevice *s;
319
320	iobase = it->options[0];
321	printk(KERN_INFO "comedi%d: parport: 0x%04lx ", dev->minor, iobase);
322	if (!request_region(iobase, PARPORT_SIZE, "parport (comedi)")) {
323		printk(KERN_ERR "I/O port conflict\n");
324		return -EIO;
325	}
326	dev->iobase = iobase;
327
328	irq = it->options[1];
329	if (irq) {
330		printk(KERN_INFO " irq=%u", irq);
331		ret = request_irq(irq, parport_interrupt, 0, "comedi_parport",
332				  dev);
333		if (ret < 0) {
334			printk(KERN_ERR " irq not available\n");
335			return -EINVAL;
336		}
337		dev->irq = irq;
338	}
339	dev->board_name = "parport";
340
341	ret = alloc_subdevices(dev, 4);
342	if (ret < 0)
343		return ret;
344	ret = alloc_private(dev, sizeof(struct parport_private));
345	if (ret < 0)
346		return ret;
347
348	s = dev->subdevices + 0;
349	s->type = COMEDI_SUBD_DIO;
350	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
351	s->n_chan = 8;
352	s->maxdata = 1;
353	s->range_table = &range_digital;
354	s->insn_bits = parport_insn_a;
355	s->insn_config = parport_insn_config_a;
356
357	s = dev->subdevices + 1;
358	s->type = COMEDI_SUBD_DI;
359	s->subdev_flags = SDF_READABLE;
360	s->n_chan = 5;
361	s->maxdata = 1;
362	s->range_table = &range_digital;
363	s->insn_bits = parport_insn_b;
364
365	s = dev->subdevices + 2;
366	s->type = COMEDI_SUBD_DO;
367	s->subdev_flags = SDF_WRITABLE;
368	s->n_chan = 4;
369	s->maxdata = 1;
370	s->range_table = &range_digital;
371	s->insn_bits = parport_insn_c;
372
373	s = dev->subdevices + 3;
374	if (irq) {
375		dev->read_subdev = s;
376		s->type = COMEDI_SUBD_DI;
377		s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
378		s->n_chan = 1;
379		s->maxdata = 1;
380		s->range_table = &range_digital;
381		s->insn_bits = parport_intr_insn;
382		s->do_cmdtest = parport_intr_cmdtest;
383		s->do_cmd = parport_intr_cmd;
384		s->cancel = parport_intr_cancel;
385	} else {
386		s->type = COMEDI_SUBD_UNUSED;
387	}
388
389	devpriv->a_data = 0;
390	outb(devpriv->a_data, dev->iobase + PARPORT_A);
391	devpriv->c_data = 0;
392	outb(devpriv->c_data, dev->iobase + PARPORT_C);
393
394	printk(KERN_INFO "\n");
395	return 1;
396}
397
398static int parport_detach(struct comedi_device *dev)
399{
400	printk(KERN_INFO "comedi%d: parport: remove\n", dev->minor);
401
402	if (dev->iobase)
403		release_region(dev->iobase, PARPORT_SIZE);
404
405	if (dev->irq)
406		free_irq(dev->irq, dev);
407
408	return 0;
409}
410
411MODULE_AUTHOR("Comedi http://www.comedi.org");
412MODULE_DESCRIPTION("Comedi low-level driver");
413MODULE_LICENSE("GPL");
414