comedi_parport.c revision 652dd4e7c78a2ad1fcf38c12e8be69651922c690
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/ioport.h>
86
87#define PARPORT_SIZE 3
88
89#define PARPORT_A 0
90#define PARPORT_B 1
91#define PARPORT_C 2
92
93static int parport_attach(comedi_device *dev, comedi_devconfig *it);
94static int parport_detach(comedi_device *dev);
95static comedi_driver driver_parport = {
96      .driver_name =	"comedi_parport",
97      .module =		THIS_MODULE,
98      .attach =		parport_attach,
99      .detach =		parport_detach,
100};
101
102COMEDI_INITCLEANUP(driver_parport);
103
104struct parport_private {
105	unsigned int a_data;
106	unsigned int c_data;
107	int enable_irq;
108};
109#define devpriv ((struct parport_private *)(dev->private))
110
111static int parport_insn_a(comedi_device *dev, comedi_subdevice *s,
112			  comedi_insn *insn, lsampl_t *data)
113{
114	if (data[0]) {
115		devpriv->a_data &= ~data[0];
116		devpriv->a_data |= (data[0] & data[1]);
117
118		outb(devpriv->a_data, dev->iobase + PARPORT_A);
119	}
120
121	data[1] = inb(dev->iobase + PARPORT_A);
122
123	return 2;
124}
125
126static int parport_insn_config_a(comedi_device *dev, comedi_subdevice *s,
127				 comedi_insn *insn, lsampl_t *data)
128{
129	if (data[0]) {
130		s->io_bits = 0xff;
131		devpriv->c_data &= ~(1 << 5);
132	} else {
133		s->io_bits = 0;
134		devpriv->c_data |= (1 << 5);
135	}
136	outb(devpriv->c_data, dev->iobase + PARPORT_C);
137
138	return 1;
139}
140
141static int parport_insn_b(comedi_device *dev, comedi_subdevice *s,
142			  comedi_insn *insn, lsampl_t *data)
143{
144	if (data[0]) {
145		/* should writes be ignored? */
146		/* anyone??? */
147	}
148
149	data[1] = (inb(dev->iobase + PARPORT_B) >> 3);
150
151	return 2;
152}
153
154static int parport_insn_c(comedi_device *dev, comedi_subdevice *s,
155			  comedi_insn *insn, lsampl_t *data)
156{
157	data[0] &= 0x0f;
158	if (data[0]) {
159		devpriv->c_data &= ~data[0];
160		devpriv->c_data |= (data[0] & data[1]);
161
162		outb(devpriv->c_data, dev->iobase + PARPORT_C);
163	}
164
165	data[1] = devpriv->c_data & 0xf;
166
167	return 2;
168}
169
170static int parport_intr_insn(comedi_device *dev, comedi_subdevice *s,
171			     comedi_insn *insn, lsampl_t *data)
172{
173	if (insn->n < 1)
174		return -EINVAL;
175
176	data[1] = 0;
177	return 2;
178}
179
180static int parport_intr_cmdtest(comedi_device *dev, comedi_subdevice *s,
181				comedi_cmd *cmd)
182{
183	int err = 0;
184	int tmp;
185
186	/* step 1 */
187
188	tmp = cmd->start_src;
189	cmd->start_src &= TRIG_NOW;
190	if (!cmd->start_src || tmp != cmd->start_src)
191		err++;
192
193	tmp = cmd->scan_begin_src;
194	cmd->scan_begin_src &= TRIG_EXT;
195	if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
196		err++;
197
198	tmp = cmd->convert_src;
199	cmd->convert_src &= TRIG_FOLLOW;
200	if (!cmd->convert_src || tmp != cmd->convert_src)
201		err++;
202
203	tmp = cmd->scan_end_src;
204	cmd->scan_end_src &= TRIG_COUNT;
205	if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
206		err++;
207
208	tmp = cmd->stop_src;
209	cmd->stop_src &= TRIG_NONE;
210	if (!cmd->stop_src || tmp != cmd->stop_src)
211		err++;
212
213	if (err)
214		return 1;
215
216	/* step 2: ignored */
217
218	if (err)
219		return 2;
220
221	/* step 3: */
222
223	if (cmd->start_arg != 0) {
224		cmd->start_arg = 0;
225		err++;
226	}
227	if (cmd->scan_begin_arg != 0) {
228		cmd->scan_begin_arg = 0;
229		err++;
230	}
231	if (cmd->convert_arg != 0) {
232		cmd->convert_arg = 0;
233		err++;
234	}
235	if (cmd->scan_end_arg != 1) {
236		cmd->scan_end_arg = 1;
237		err++;
238	}
239	if (cmd->stop_arg != 0) {
240		cmd->stop_arg = 0;
241		err++;
242	}
243
244	if (err)
245		return 3;
246
247	/* step 4: ignored */
248
249	if (err)
250		return 4;
251
252	return 0;
253}
254
255static int parport_intr_cmd(comedi_device *dev, comedi_subdevice *s)
256{
257	devpriv->c_data |= 0x10;
258	outb(devpriv->c_data, dev->iobase + PARPORT_C);
259
260	devpriv->enable_irq = 1;
261
262	return 0;
263}
264
265static int parport_intr_cancel(comedi_device *dev, comedi_subdevice *s)
266{
267	printk(KERN_DEBUG "parport_intr_cancel()\n");
268
269	devpriv->c_data &= ~0x10;
270	outb(devpriv->c_data, dev->iobase + PARPORT_C);
271
272	devpriv->enable_irq = 0;
273
274	return 0;
275}
276
277static irqreturn_t parport_interrupt(int irq, void *d PT_REGS_ARG)
278{
279	comedi_device *dev = d;
280	comedi_subdevice *s = dev->subdevices + 3;
281
282	if (!devpriv->enable_irq) {
283		printk(KERN_ERR "comedi_parport: bogus irq, ignored\n");
284		return IRQ_NONE;
285	}
286
287	comedi_buf_put(s->async, 0);
288	s->async->events |= COMEDI_CB_BLOCK | COMEDI_CB_EOS;
289
290	comedi_event(dev, s);
291	return IRQ_HANDLED;
292}
293
294static int parport_attach(comedi_device *dev, comedi_devconfig *it)
295{
296	int ret;
297	unsigned int irq;
298	unsigned long iobase;
299	comedi_subdevice *s;
300
301	iobase = it->options[0];
302	printk(KERN_INFO "comedi%d: parport: 0x%04lx ", dev->minor, iobase);
303	if (!request_region(iobase, PARPORT_SIZE, "parport (comedi)")) {
304		printk("I/O port conflict\n");
305		return -EIO;
306	}
307	dev->iobase = iobase;
308
309	irq = it->options[1];
310	if (irq) {
311		printk(" irq=%u", irq);
312		ret = comedi_request_irq(irq, parport_interrupt, 0,
313			"comedi_parport", dev);
314		if (ret < 0) {
315			printk(" irq not available\n");
316			return -EINVAL;
317		}
318		dev->irq = irq;
319	}
320	dev->board_name = "parport";
321
322	ret = alloc_subdevices(dev, 4);
323	if (ret < 0)
324		return ret;
325	ret = alloc_private(dev, sizeof(struct parport_private));
326	if (ret < 0)
327		return ret;
328
329	s = dev->subdevices + 0;
330	s->type = COMEDI_SUBD_DIO;
331	s->subdev_flags = SDF_READABLE | SDF_WRITABLE;
332	s->n_chan = 8;
333	s->maxdata = 1;
334	s->range_table = &range_digital;
335	s->insn_bits = parport_insn_a;
336	s->insn_config = parport_insn_config_a;
337
338	s = dev->subdevices + 1;
339	s->type = COMEDI_SUBD_DI;
340	s->subdev_flags = SDF_READABLE;
341	s->n_chan = 5;
342	s->maxdata = 1;
343	s->range_table = &range_digital;
344	s->insn_bits = parport_insn_b;
345
346	s = dev->subdevices + 2;
347	s->type = COMEDI_SUBD_DO;
348	s->subdev_flags = SDF_WRITABLE;
349	s->n_chan = 4;
350	s->maxdata = 1;
351	s->range_table = &range_digital;
352	s->insn_bits = parport_insn_c;
353
354	s = dev->subdevices + 3;
355	if (irq) {
356		dev->read_subdev = s;
357		s->type = COMEDI_SUBD_DI;
358		s->subdev_flags = SDF_READABLE | SDF_CMD_READ;
359		s->n_chan = 1;
360		s->maxdata = 1;
361		s->range_table = &range_digital;
362		s->insn_bits = parport_intr_insn;
363		s->do_cmdtest = parport_intr_cmdtest;
364		s->do_cmd = parport_intr_cmd;
365		s->cancel = parport_intr_cancel;
366	} else {
367		s->type = COMEDI_SUBD_UNUSED;
368	}
369
370	devpriv->a_data = 0;
371	outb(devpriv->a_data, dev->iobase + PARPORT_A);
372	devpriv->c_data = 0;
373	outb(devpriv->c_data, dev->iobase + PARPORT_C);
374
375	printk("\n");
376	return 1;
377}
378
379static int parport_detach(comedi_device *dev)
380{
381	printk("comedi%d: parport: remove\n", dev->minor);
382
383	if (dev->iobase)
384		release_region(dev->iobase, PARPORT_SIZE);
385
386	if (dev->irq)
387		comedi_free_irq(dev->irq, dev);
388
389	return 0;
390}
391